Flutter中使用GetX从0到1搭建自已的项目框架(三)

1,188 阅读8分钟

在上一篇《Flutter中使用GetX从0到1搭建自己的项目框架(二)》中,我们学习了如何注册路由和使用路由的相关方法。今天,我们将继续深入,探讨如何构建页面。页面是应用程序中最基本的组件之一,它展示了用户界面的内容和交互。在Flutter中,使用GetX框架可以轻松构建页面,并实现页面间的状态管理和数据传递。让我们开始编写一个简单的页面。

下面我们就以登录页面为例展示如何使用GetX 的Controller来构建一个mvvm模式的应用。

先来看一下Example的项目目录是如何划分的: 在应用中有很多不同的业务模块我们统称为module,在每个module下我们又为具体的业务划分了login/controller login/view login/model

image.png

下面我们一起来看一下登录模块是怎么实现的;

login/view/login_page.dart

class LoginPage extends EdenBaseWidget<LoginController> with LoginView {
  LoginPage({Key? key}) : super(key: key);

  @override
  String toolbarTitle() {
    return "Login";
  }

  void _onLogin() {
    showLoading(controller.onLogin()).then((value) {
      if (value == true) {
        EdenRoute.back();
      } else {}
    });
  }

  @override
  Widget buildBody(BuildContext context, LoginController _controller) {
    return renderView(context, _controller, _onLogin);
  }
}

LoginPage中继承了flutter_eden中EdenBaseWidget<T>父类;

EdenBaseWidget<T>中我们对GetX GetView<T>进行了封装; 页面的具体实现在GetBuilder<T>中:

@override
  Widget build(BuildContext context) {
    return GetBuilder<T>(
        init: controller,
        builder: (controller) {
          return Scaffold(
            appBar: appToolbar(context),
            body: buildBody(context, controller),
            backgroundColor: getBackgroundColor(),
            floatingActionButton: floatingActionButton(),
            bottomNavigationBar: bottomNavigationBar(),
          );
        });
  }

来看一下具体的代码实现:

abstract class EdenBaseWidget<T extends EdenBaseController> extends GetView<T> {
  EdenBaseWidget({Key? key}) : super(key: key);

  ///toolbar title
  String? _toolbarTitle;

  ///hide toolbar
  bool hideToolbar() {
    return false;
  }

  /// hide toolbar arrow
  bool hideToolbarArrowBack() {
    return false;
  }

  ///tool title
  String toolbarTitle();
  Color? getBackgroundColor() {
    return kIsDark ? backgroundColor : backgroundLightColor;
  }

  void setToolbarTitle(String? toolbarTitle) {
    _toolbarTitle = toolbarTitle;
  }

  /// toolbar title color
  Color? _toolbarTitleColor;
  Color? get toolbarTitleColor => _toolbarTitleColor;
  void setToolbarTitleColor(Color? titleColor) {
    _toolbarTitleColor = titleColor;
  }

  /// toolbar background color
  Color? _toolbarBackgroundColor =
      kIsDark ? toolBarbgColor : toolBarbgLightColor;
  Color? get toolbarBackgroundColor => _toolbarBackgroundColor;
  void setToolbarBackgroundColor(Color? backgroundColor) {
    _toolbarBackgroundColor = backgroundColor;
  }

  List<Widget>? toolbarActions() {
    return [];
  }

  ///toolbar arrow back
  Widget toolbarArrowBack() {
    return IconButton(
      icon: const Icon(Icons.arrow_back),
      onPressed: () {},
    );
  }

  ///
  PreferredSizeWidget? appToolbar(BuildContext context) {
    return hideToolbar()
        ? null
        : ToolbarWidget(
            centerTitle: true,
            backgroundColor: toolbarBackgroundColor,
            hideBackArrow: hideToolbarArrowBack(),
            title: _toolbarTitle ?? toolbarTitle(),
            color: toolbarTitleColor,
            actions: toolbarActions(),
            fontSize: 35.rpx,
            fontWeight: FontWeight.w500,
          );
  }

  ///build body
  Widget buildBody(BuildContext context, T _controller);

  ///bottom navigation bar
  Widget? bottomNavigationBar() {
    return null;
  }

  Widget? floatingActionButton() {
    return null;
  }

  @override
  Widget build(BuildContext context) {
    return GetBuilder<T>(
        init: controller,
        builder: (controller) {
          return Scaffold(
            appBar: appToolbar(context),
            body: buildBody(context, controller),
            backgroundColor: getBackgroundColor(),
            floatingActionButton: floatingActionButton(),
            bottomNavigationBar: bottomNavigationBar(),
          );
        });
  }
}

EdenBaseWidget<T>它继承自 GetView<T>需要是 EdenBaseController 或其子类封装了一个通用的基础 Widget,提供了一些常用的功能和配置项,用于构建应用程序中的页面。

主要特点和方法包括:

  • _toolbarTitle:工具栏的标题。
  • hideToolbar():是否隐藏工具栏。
  • hideToolbarArrowBack():是否隐藏工具栏的返回箭头。
  • toolbarTitle():工具栏的标题文本。
  • _toolbarTitleColor:工具栏标题的颜色。
  • _toolbarBackgroundColor:工具栏的背景颜色。
  • toolbarActions():工具栏的操作按钮。
  • toolbarArrowBack():工具栏的返回箭头按钮。
  • appToolbar(BuildContext context):构建应用程序的工具栏部分。
  • buildBody(BuildContext context, T _controller):构建页面的主体部分。
  • bottomNavigationBar():底部导航栏部分。
  • floatingActionButton():浮动操作按钮部分。
  • build(BuildContext context):构建整个页面的方法。

该抽象类的目的是为了提供一个可扩展的基础 Widget,供具体页面继承并实现其中的抽象方法,以定制化页面的功能和外观。

通过使用该抽象类,可以减少重复的代码,统一页面的样式和布局,提高代码的复用性和可维护性。

LoginPage实现了EdenBaseWidget<LoginController>buildBody()方法,并混入了LoginViewrenderView()方法

 @override
  Widget buildBody(BuildContext context, LoginController _controller) {
    return renderView(context, _controller, _onLogin);
  }

LoginView登录页面的具本实现:

class LoginView {
  Widget renderView(
      BuildContext context, LoginController _controller, Function() onLogin) {
    return SingleChildScrollView(
      child: form(context, _controller, onLogin),
    );
  }

  ///
  Widget form(
      BuildContext context, LoginController _controller, Function() onLogin) {
    return Padding(
      padding: const EdgeInsets.all(dimens.margin),
      child: Padding(
        padding: const EdgeInsets.all(8.0),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const SizedBox(height: 20),
            const LogoWidget(
              small: true,
            ),
            const SizedBox(height: 20),
            InputWidget(
              placeholder: "login",
              // value: snapshot.data,
              onChanged: (value) {
                _controller.setUsername(value);
              },
            ),
            const SizedBox(height: 10),
            InputWidget(
              placeholder: "password",

              // value: snapshot.data,
              onChanged: (value) {
                _controller.setPassword(value);
              },
            ),
            const SizedBox(height: 20),
            Align(
                alignment: Alignment.centerRight,
                child: TextButton(
                  onPressed: () {},
                  child: const TextWidget(
                    text: "sign up",
                  ),
                )),
            const SizedBox(height: 12),
            SizedBox(
              width: double.infinity,
              child: ButtonWidget(
                label: "login",
                onPressed: () {
                  onLogin();
                },
              ),
            ),
          ],
        ),
      ),
    );
  }
}

接下来我们来看一下Login模块比较重要LoginController类是怎么实现的:

class LoginController extends EdenBaseController {
  final ILoginRespository loginRespository;
  LoginController({required this.loginRespository});
  String? _username;
  String? _password;
  String? get username => _username;
  String? get password => _password;

  @override
  void init() {}

  void setUsername(String? username) {
    _username = username;
  }

  void setPassword(String? password) {
    _password = password;
  }

  Future<bool?> onLogin() async {
    FocusScope.of(Get.context!).requestFocus(FocusNode());
    print("onLogin-username=$username -password=$password");

    return await loginRespository.doLogin(username, password).then((value) {
      return _loginSuccess(value);
    }).catchError((error) {
      return _loginFailure(error);
    }).then((value) async {
      print("auth refresh=$value");
      if (value == true) {
        await Get.find<AuthService>().init();
        AccountController.to.update();
      }
      return value;
    });
  }

  bool? _loginSuccess(LoginModel? login) {
    print("loginModel=${login?.toJson()}");
    if (login?.resCode == 200) {
      StorageHelper.set(StorageKeys.token, login?.data?.accessToken);
      StorageHelper.set(Constants.USER_INFO, login?.toRawJson());
      return true;
    } else {
      EdenSnackbar("${login?.message}", title: "Message");
      return false;
    }
  }

  bool _loginFailure(dynamic error) {
    print("error=$error");
    EdenSnackbar(error.toString(), title: "Error");

    return false;
  }

  @override
  void dealloc() {}
}

它继承自 EdenBaseController

主要功能:

  • ILoginRespository loginRespository:用于处理登录相关操作的仓库接口。
  • 构造函数 LoginController({required this.loginRespository}):接收一个 loginRespository 参数,并将其赋值给类的成员变量。
  • _username_password:保存用户名和密码的私有变量。
  • usernamepassword:通过 getter 方法获取用户名和密码的值。
  • init() 方法:重写基类的 init() 方法,在初始化阶段执行逻辑(这里为空)。
  • setUsername(String? username)setPassword(String? password):设置用户名和密码的方法。
  • onLogin() 方法:执行登录操作的逻辑。在该方法中,先隐藏键盘,然后打印用户名和密码。接着调用 loginRespositorydoLogin 方法进行登录,处理登录成功和失败的情况,并返回一个 Future<bool?> 对象。在登录成功后,保存用户的访问令牌和其他信息,并返回 true,否则返回 false
  • _loginSuccess(LoginModel? login) 方法:处理登录成功的情况。如果登录的返回码为 200,将用户的访问令牌和其他信息存储在本地,并返回 true,否则显示登录失败的提示,并返回 false
  • _loginFailure(dynamic error) 方法:处理登录失败的情况。显示错误提示,并返回 false
  • dealloc() 方法:重写基类的 dealloc() 方法,在销毁阶段执行逻辑(这里为空)。

LoginController 类的目的是作为登录页面的控制器,处理登录逻辑和状态,并与仓库层进行交互,实现登录功能。

EdenBaseController的实现:

abstract class EdenBaseController extends GetxController {
  bool? _isRequest = false;

  bool? get isRequest => _isRequest;

  Future futureDelayed(
    Future computation, {
    int seconds = 1,
  }) {
    return Future.delayed(
        Duration(
          seconds: seconds,
        ),
        () => computation);
  }

  @override
  void onInit() {
    super.onInit();
    init();
    if (isRequest == true) {
      showLoading(doRequest(true));
    }
  }

  @override
  void onClose() {
    super.onClose();
    dealloc();
  }

  void init();
  void dealloc();
  Future doRequest(bool isPull) async {}

  void hideKeyboard() {
    FocusScope.of(kContext!).requestFocus(FocusNode());
  }

  ///
  S edenFind<S>({String? tag}) => Get.find<S>(tag: tag);
}

EdenBaseController 它继承自 GetxController

主要功能:

  • _isRequest:表示是否正在进行请求的私有变量,默认为 false
  • isRequest:通过 getter 方法获取 _isRequest 的值。
  • futureDelayed(Future computation, {int seconds = 1}):延迟执行给定的计算任务,返回一个 Future 对象。
  • onInit() 方法:重写基类的 onInit() 方法,在初始化阶段执行逻辑。调用 init() 方法进行初始化,并根据 isRequest 的值显示加载状态。
  • onClose() 方法:重写基类的 onClose() 方法,在关闭阶段执行逻辑。调用 dealloc() 方法进行清理操作。
  • init() 方法:抽象方法,需要在具体的子类中实现。用于执行初始化逻辑。
  • dealloc() 方法:抽象方法,需要在具体的子类中实现。用于执行清理操作。
  • doRequest(bool isPull) 方法:抽象方法,需要在具体的子类中实现。用于执行请求操作,具体的实现可以根据参数 isPull 判断是下拉刷新还是加载更多。
  • hideKeyboard() 方法:隐藏键盘的方法。
  • edenFind<S>({String? tag}) 方法:通过 GetX 的 Get.find() 方法获取指定类型的实例对象。可以通过指定 tag 来获取特定的实例。

EdenBaseWidget 和 EdenBaseController 是用于构建基于 GetX 状态管理的 Widget 和 Controller 的抽象类:

EdenBaseWidget:

  • EdenBaseWidget 是一个抽象类,用于创建具有一致性和可定制外观的 Widget。
  • 提供了设置工具栏标题、背景色、文字颜色等属性的方法,以便根据需要进行定制。
  • 可以通过重写方法 hideToolbar() 和 hideToolbarArrowBack() 来控制是否隐藏工具栏和返回箭头。
  • 通过实现 toolbarTitle() 方法来设置工具栏标题,或通过 setToolbarTitle() 方法动态设置标题。
  • 可以通过重写 toolbarActions() 方法添加工具栏操作按钮。
  • 提供了 appToolbar() 方法来构建应用程序的顶部工具栏,可以根据需求设置背景色、标题颜色和其他属性。
  • 通过重写 buildBody() 方法来构建主体内容,可以根据数据状态和业务需求自定义 UI 元素。
  • 支持设置底部导航栏和浮动操作按钮。
  • 提供了生命周期方法 init() 和 dealloc(),用于在 Widget 的生命周期中执行初始化和资源释放操作。

EdenBaseController:

  • EdenBaseController 是一个抽象类,用于管理基于 GetX 的数据状态和业务逻辑。
  • 继承自 GetX 的 GetxController 类,提供了强大的状态管理功能。
  • 可以定义数据状态,例如 isRequest 属性用于表示是否处于请求状态。
  • 提供了 futureDelayed() 方法,用于延迟执行异步操作,方便处理延迟加载等场景。
  • 在 onInit() 方法中执行初始化操作,可以在此处调用 init() 方法进行一些初始化设置。
  • 在 onClose() 方法中执行资源释放操作,可以在此处调用 dealloc() 方法释放相关资源。
  • 提供了 hideKeyboard() 方法,用于隐藏键盘。
  • 可以通过 edenFind() 方法来获取依赖对象,方便在 Controller 中访问其他对象或服务。

通过继承 EdenBaseWidget 和 EdenBaseController,开发人员可以实现具体的 Widget 和 Controller,并根据项目需求来定制和扩展其功能。这种抽象类的使用方式可以提高代码的重用性、可维护性和可测试性,同时也有助于保持代码结构的一致性和清晰性。

由于篇幅原因今天就分享到这里,后续会连载!

如果您对此项目感兴趣可以加入一起维护开发!

开源项目地址:flutter_eden

希望对您有所帮助谢谢!!