用户管理器

创建 Ubuntu Linux Flutter 应用教程,使用 yaru 主题。

简介

你将学到什么

  • 在Ubuntu中设置Flutter开发环境
  • 使用yaru主题为Linux桌面构建Flutter应用程序
  • 使用json-server作为本地后端来存储数据

你将构建什么

09_final_app_dark


设置你的Flutter环境

在Ubuntu上安装Flutter并启用Flutter Linux桌面支持

sudo snap install flutter
flutter channel beta
flutter upgrade
flutter config --enable-linux-desktop

安装和设置Visual Studio Code

要在Ubuntu中安装VS Code,请打开终端并运行以下命令

sudo snap install code --classic

启动VS Code快速打开(Ctrl+P),粘贴以下命令……

ext install Dart-Code.flutter

……然后按Enter键安装官方Flutter VS Code扩展

开始

打开终端或VS Code集成终端,然后运行以下命令为虚构组织org.flutterfans创建一个新的Flutter项目

flutter create --org org.flutterfans user_manager

用VS Code打开您刚刚创建的项目目录user_manager

00_open

您现在应该看到以下目录(1)以及正在使用的“Linux (linux-x64)”(2)设备,在启用Linux桌面支持后,这在Linux上是默认的。

01_directory_overview

让我们通过打开lib目录(1)并点击void main()(2)上方的“运行”小标签来测试应用程序是否可以启动。

02_first_launch

使用Yaru主题

打开pubspec.yaml - 用于管理应用程序生命周期的文件 - 并在dependencies部分添加yaru: ^0.0.6,这样您将得到以下依赖项(请确保yaruflutter在同一列)。

dependencies:
  flutter:
    sdk: flutter
  yaru: ^0.0.6

您现在可以通过在main.dart的顶部添加以下内容来导入yaru主题:

import 'package:yaru/yaru.dart' as yaru;

通过将MaterialApptheme属性设置为yaru.lightTheme,并将darkTheme属性设置为yaru.darkTheme,在您的应用程序中使用yaru主题,这样您将得到以下类定义 - 您的应用程序现在将遵循您系统的外观。

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: yaru.lightTheme,
      darkTheme: yaru.darkTheme,
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

04_toggle_themes

构建应用程序

组织您的项目结构

您可能需要稍微组织一下项目的目录结构。首先,在lib目录下创建一个view目录,方法是右键单击lib目录,然后单击“新建文件夹”菜单项,并在弹出的文本字段中输入view

05_create_view_dir

modelservice目录重复此步骤,以便在main.dart之上的目录树中得到以下结构。

06_project_structure

一个简单的用户模型

为了方便地在应用程序和服务器之间发送用户数据,您需要一个用户类,其中只包含用户的数据属性。

右键单击model目录,然后单击“新建文件”,并将其命名为user.dart

07_new_file

插入以下代码

class User {
  int? id;
  String name;

  User({required this.id, required this.name});

  User.empty() : this(id: null, name: "");

  factory User.fromJson(Map<String, dynamic> json) =>
      User(id: json['id'], name: json['name']);

  Map<String, dynamic> toJson() => {"id": id, "name": name};
}

用户服务

为了让您的应用程序通过HTTP与您的服务器通信,请在pubspec.yamldependencies部分添加http: ^0.13.1,在service目录中创建user_service.dart,并将以下代码粘贴到其中。

import 'dart:convert';

import 'package:http/http.dart' as http;
import 'package:user_manager/model/user.dart';

class UserService {
  UserService({required this.uri});

  final String uri;

  Future<List<User>> getUsers() async {
    final response = await http.get(Uri.http(uri, 'users'));

    if (response.statusCode == 200) {
      var usersJson = jsonDecode(response.body.toString()) as List;
      return usersJson.map((json) => User.fromJson(json)).toList();
    } else {
      throw Exception(response.statusCode.toString() + ': ' + response.body);
    }
  }

  Future saveUser(User user) async {
    for (var aUser in await getUsers()) {
      if (aUser.id == user.id) {
        await updateUser(user);
        return;
      }
    }
    await addUser(user);
  }

  Future<http.Response> addUser(User user) async {
    return await http.post(Uri.http(uri, 'users'),
        headers: <String, String>{
          'Content-Type': 'application/json; charset=UTF-8',
        },
        body: jsonEncode(user.toJson()));
  }

  Future updateUser(User user) async {
    return await http.put(Uri.http(uri, 'users/${user.id}'),
        headers: <String, String>{
          'Content-Type': 'application/json; charset=UTF-8',
        },
        body: jsonEncode(user.toJson()));
  }

  Future removeUser(User user) async {
    await http.delete((Uri.http(uri, 'users/${user.id}')));
  }
}

用户界面

每个用户一张卡片

将被管理的每个用户都由UI中的一张卡片表示。

view目录中创建user_card.dart,并将以下代码粘贴到其中。

import 'package:flutter/material.dart';
import 'package:user_manager/model/user.dart';

class UserCard extends StatefulWidget {
  final User user;
  final VoidCallback onDelete;
  final VoidCallback onEdit;

  UserCard({required this.user, required this.onDelete, required this.onEdit});

  @override
  _UserCardState createState() => _UserCardState();
}

class _UserCardState extends State<UserCard> {
  @override
  Widget build(BuildContext context) {
    return Card(
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: <Widget>[
          ListTile(
            leading: Icon(Icons.person),
            title: Text(widget.user.name),
          ),
          Row(
            mainAxisAlignment: MainAxisAlignment.end,
            children: <Widget>[
              TextButton(
                child: const Text('Edit'),
                onPressed: () => widget.onEdit(),
              ),
              const SizedBox(width: 8),
              TextButton(
                child: const Text('Remove'),
                onPressed: () => widget.onDelete(),
              ),
              const SizedBox(width: 8),
            ],
          ),
        ],
      ),
    );
  }
}

用于编辑用户的对话框

用户将在对话框中进行编辑,该对话框通过单击巨大的绿色浮动操作按钮或用户卡片的编辑按钮弹出。

view目录中创建user_edit_dialog.dart文件,并将以下代码复制/粘贴到其中。

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';

class UserEditDialog extends StatefulWidget {
  final TextEditingController nameController;
  final AsyncCallback editUser;

  UserEditDialog({required this.nameController, required this.editUser});

  @override
  _UserEditDialogState createState() => _UserEditDialogState();
}

class _UserEditDialogState extends State<UserEditDialog> {
  @override
  Widget build(BuildContext context) {
    return Dialog(
      child: Container(
        height: 150,
        width: 200,
        child: Padding(
          padding: const EdgeInsets.all(10.0),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.start,
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Padding(
                padding: const EdgeInsets.all(8.0),
                child: TextField(
                  autofocus: true,
                  controller: widget.nameController,
                  decoration: InputDecoration(hintText: 'User name'),
                ),
              ),
              Row(
                children: [
                  Padding(
                    padding: const EdgeInsets.all(8.0),
                    child: ElevatedButton(
                      onPressed: () async =>
                          Navigator.of(context).pop(widget.editUser()),
                      child: Text(
                        "Save",
                        style: TextStyle(color: Colors.white),
                      ),
                    ),
                  ),
                  Padding(
                    padding: const EdgeInsets.all(8.0),
                    child: TextButton(
                      onPressed: () => Navigator.of(context).pop(),
                      child: Text("Cancel"),
                    ),
                  )
                ],
              ),
            ],
          ),
        ),
      ),
    );
  }
}

新的主页

旧的主页将不足以满足需求,因此您需要一个新的主页来加载用户卡片。主页使用用户服务来管理用户数据。

再次打开您的pubspec.yaml文件,并在dependencies部分添加injector: ^2.0.0。在view目录中创建home_page.dart文件,并将以下代码粘贴进去。

import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:injector/injector.dart';
import 'package:user_manager/model/user.dart';
import 'package:user_manager/service/user_service.dart';
import 'package:user_manager/view/user_card.dart';
import 'package:user_manager/view/user_edit_dialog.dart';

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  late TextEditingController _nameController;
  final _userService = Injector.appInstance.get<UserService>();

  @override
  void initState() {
    _nameController = TextEditingController();
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Home'),
      ),
      body: Center(
        child: Padding(
          padding: const EdgeInsets.all(8.0),
          child: Scrollbar(
            child: FutureBuilder(
                future: _userService.getUsers(),
                builder: (context, AsyncSnapshot<List<User>> snapShot) {
                  if (snapShot.hasData) {
                    return ListView(
                      children: snapShot.data!
                          .map((user) => UserCard(
                              user: user,
                              onDelete: () {
                                _userService
                                    .removeUser(user)
                                    .then((value) => setState(() {
                                          _showSnackBar(
                                              'Deleted user: ' + user.name);
                                        }))
                                    .onError((error, stackTrace) =>
                                        _showSnackBar(error.toString()));
                              },
                              onEdit: () => editUser(user, context)))
                          .toList(),
                    );
                  }
                  return CircularProgressIndicator();
                }),
          ),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.add),
        onPressed: () {
          _nameController.text = "";
          editUser(new User.empty(), context);
        },
      ),
    );
  }

  void editUser(User user, BuildContext context) {
    _nameController.text = user.name;
    showDialog(
        context: context,
        barrierDismissible: false,
        builder: (BuildContext context) {
          return UserEditDialog(
              nameController: _nameController,
              editUser: () async {
                user.name = _nameController.text;
                _userService
                    .saveUser(user)
                    .then((value) => setState(() {
                          _showSnackBar('Saved user: ' + user.name);
                        }))
                    .onError(
                        (error, stackTrace) => _showSnackBar(error.toString()));
              });
        });
  }

  void _showSnackBar(String message) {
    ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(duration: const Duration(seconds: 1), content: Text(message)));
  }
}

完成您的main.dart

测试应用程序的用户界面不再需要了。删除MyApp以外的所有Widgets,将home属性更改为HomePage(),将您的main()函数更改为异步的,返回一个Future<void>,并在运行应用程序之前使用Injector类注册您的UserService。为了确保您已正确导入所有内容,请用以下新版本替换main.dart中的所有代码。

import 'package:flutter/material.dart';
import 'package:injector/injector.dart';
import 'package:user_manager/service/user_service.dart';
import 'package:user_manager/view/home_page.dart';
import 'package:yaru/yaru.dart' as yaru;

Future<void> main() async {
  final userService = UserService(uri: 'localhost:3000');
  Injector.appInstance.registerDependency<UserService>(() => userService);
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: yaru.lightTheme,
      darkTheme: yaru.darkTheme,
      home: HomePage(),
    );
  }
}

创建一个本地服务器来处理客户端请求

创建模拟数据

在应用程序的根文件夹中创建db.json,并在json格式中插入一些虚构用户。

{
    "users": [
        {
            "id": 1,
            "name": "Carlo"
        },
        {
            "id": 2,
            "name": "Mads"
        },
        {
            "id": 3,
            "name": "Frederik"
        },
        {
            "id": 4,
            "name": "Stuart"
        },
        {
            "id": 5,
            "name": "Paul"
        },
        {
            "id": 6,
            "name": "Muq"
        }
    ]
}

安装node-js和json-server

我们将使用一个出色的本地模拟服务器让我们的客户端与之通信。为此,我们需要node snap。输入以下命令来安装node和json-server。

sudo snap install node --classic
sudo npm install -g json-server

进入用户管理 :)

通过运行以下命令来启动您的json-server

json-server --watch db.json

再次启动您的应用程序!完成 :) 您现在可以使用yaru主题在Ubuntu Linux应用程序中管理用户。

08_final_app

GitHub

https://github.com/ubuntu/user_manager