Flutter MVVM架构插件library_architecture_mvvm_modify的使用

发布于 1周前 作者 nodeper 来自 Flutter

Flutter MVVM架构插件library_architecture_mvvm_modify的使用

介绍

library_architecture_mvvm_modify 是一个用于在 Flutter 中实现 MVVM(Model-View-ViewModel)架构的插件。它提供了一套规则和模板来帮助开发者更好地组织和管理代码。

开始使用

示例项目

你可以查看一个简单的示例项目,以便更好地理解如何使用这个插件:

如何创建基于此架构的项目?

你可以使用 GitHub 模板来快速创建项目:

文档

在阅读文档之前,请务必先阅读整个示例项目。

架构对象

以下是架构对象的列表及其继承关系和重构规则:

class NamedUtility {}
class NamedVM {}
class DataForNamed {}
class EnumDataForNamed {}
class ModelTTNamedTTNamedTTNamedTTIterator {}
class NamedException {}
class NamedState {}
class NamedStreamWState {}
class Model {}
class ListModel {}
class NamedService {}
class ModelWrapper {}
class ListModelWrapper {}
class ModelWrapperRepository {}
class TempCacheProvider {}
class ExceptionController {}
class Result {}
class ResultWithModelWrapper {}
class ResultWithListModelsWrapper {}
class CurrentModelWIndex {}
class IDispose {}

示例代码

以下是一个完整的示例代码,展示了如何使用 library_architecture_mvvm_modify 插件。

import 'dart:convert';
import 'package:library_architecture_mvvm_modify/library_architecture_mvvm_modify.dart';
import 'package:http/http.dart' as http;
import 'package:meta/meta.dart';

@immutable
final class FactoryModelWrapperRepositoryUtility {
  const FactoryModelWrapperRepositoryUtility._();

  static IPAddressWrapperRepository
      getIPAddressWrapperRepositoryFromNamedHttpClientService(
          BaseNamedHttpClientService namedHttpClientService) {
    return IPAddressWrapperRepository(namedHttpClientService);
  }
}

@immutable
final class KeysUrlEndpointUtility {
  /* JsonipAPI */
  static const String jsonipAPI = "https://jsonip.com";
  static const String jsonipAPIQQProviders = "$jsonipAPI/providers";

  const KeysUrlEndpointUtility._();
}

@immutable
final class ReadyDataUtility {
  static const String unknown = "unknown";
  static const String success = "success";

  const ReadyDataUtility._();
}

@immutable
final class KeysHttpClientServiceUtility {
  /* IPAddress */
  static const String iPAddressQQIp = "ip";

  const KeysHttpClientServiceUtility._();
}

@immutable
base class IPAddress extends BaseModel {
  final String ip;

  const IPAddress(this.ip) : super(ip);

  @override
  IPAddress clone() => IPAddress(ip);

  @override
  String toString() {
    return "IPAddress(ip: $ip)";
  }
}

@immutable
base class ListIPAddress<T extends IPAddress> extends BaseListModel<T> {
  const ListIPAddress(super.listModel) : super();

  @override
  ListIPAddress<T> clone() {
    List<T> newListModel = List.empty(growable: true);
    for (final T model in listModel) {
      newListModel.add(model.clone() as T);
    }
    return ListIPAddress<T>(newListModel);
  }

  @override
  String toString() {
    String strListModel = "\n";
    for (final T itemModel in listModel) {
      strListModel += "$itemModel,\n";
    }
    return "ListIPAddress(listModel: [$strListModel])";
  }
}

@immutable
base class IPAddressWrapper extends BaseModelWrapper {
  const IPAddressWrapper(super.listObject);

  @override
  IPAddress createModel() {
    return IPAddress(listObject[0]);
  }
}

@immutable
base class ListIPAddressWrapper extends BaseListModelWrapper {
  const ListIPAddressWrapper(super.listsListObject);

  @override
  ListIPAddress createListModel() {
    final List<IPAddress> listModel = List.empty(growable: true);
    for (final List<dynamic> itemListObject in listsListObject) {
      final iPAddressWrapper = IPAddressWrapper(itemListObject);
      listModel.add(iPAddressWrapper.createModel());
    }
    return ListIPAddress(listModel);
  }
}

abstract class BaseNamedHttpClient {
  const BaseNamedHttpClient();

  Future<http.Response> get(Uri url, {Map<String, String>? headers});

  Future<http.Response> post(Uri url,
      {Map<String, String>? headers, body, Encoding? encoding});

  Future<void> close();
}

final class DefaultHttpClient extends BaseNamedHttpClient {
  final http.Client _client;

  const DefaultHttpClient(this._client);

  @override
  Future<http.Response> get(Uri url, {Map<String, String>? headers}) {
    return _client.get(url, headers: headers);
  }

  @override
  Future<http.Response> post(Uri url,
      {Map<String, String>? headers, Object? body, Encoding? encoding}) {
    return _client.post(url, headers: headers, body: body, encoding: encoding);
  }

  @override
  Future<void> close() async {
    _client.close();
  }
}

final class TimeoutHttpClient extends BaseNamedHttpClient {
  final http.Client _client;
  final Duration _timeout;

  const TimeoutHttpClient(this._client, this._timeout);

  @override
  Future<http.Response> get(Uri url, {Map<String, String>? headers}) {
    return _client.get(url, headers: headers).timeout(_timeout);
  }

  @override
  Future<http.Response> post(Uri url,
      {Map<String, String>? headers, Object? body, Encoding? encoding}) {
    return _client
        .post(url, headers: headers, body: body, encoding: encoding)
        .timeout(_timeout);
  }

  @override
  Future<void> close() async {
    _client.close();
  }
}

abstract class BaseNamedHttpClientService {
  const BaseNamedHttpClientService();

  BaseNamedHttpClient? get getParameterNamedHttpClient;
}

final class DefaultHttpClientService extends BaseNamedHttpClientService {
  static final DefaultHttpClientService instance = DefaultHttpClientService._();
  BaseNamedHttpClient? _namedHttpClient;

  DefaultHttpClientService._();

  @override
  BaseNamedHttpClient? get getParameterNamedHttpClient {
    if (_namedHttpClient != null) {
      return _namedHttpClient;
    }
    _namedHttpClient = DefaultHttpClient(http.Client());
    return _namedHttpClient;
  }
}

final class TimeoutHttpClientService extends BaseNamedHttpClientService {
  static final TimeoutHttpClientService instance = TimeoutHttpClientService._();
  BaseNamedHttpClient? _namedHttpClient;

  TimeoutHttpClientService._();

  @override
  BaseNamedHttpClient? get getParameterNamedHttpClient {
    if (_namedHttpClient != null) {
      return _namedHttpClient;
    }
    _namedHttpClient =
        TimeoutHttpClient(http.Client(), const Duration(seconds: 5));
    return _namedHttpClient;
  }
}

@immutable
base class IPAddressWrapperRepository<T extends IPAddressWrapper,
    Y extends ListIPAddressWrapper> extends BaseModelWrapperRepository {
  @protected
  final BaseNamedHttpClientService namedHttpClientService;

  const IPAddressWrapperRepository(this.namedHttpClientService);

  @override
  void dispose() {}

  Future<ResultWithModelWrapper<T>> getIPAddressParameterNamedHttpClientService() async {
    try {
      final response = await namedHttpClientService.getParameterNamedHttpClient
          ?.get(Uri.parse(KeysUrlEndpointUtility.jsonipAPI));
      if (response?.statusCode != 200) {
        throw NetworkException.fromKeyAndStatusCode(this,
            response?.statusCode.toString() ?? "", response?.statusCode ?? 0);
      }
      final Map<String, dynamic> data = jsonDecode(response?.body ?? "");
      final ipByIPAddress = getSafeValueFromMapAndKeyAndDefaultValue(
          data, KeysHttpClientServiceUtility.iPAddressQQIp, "");
      return ResultWithModelWrapper.success(
          IPAddressWrapper([ipByIPAddress]) as T);
    } on NetworkException catch (e) {
      return ResultWithModelWrapper.exception(e);
    } catch (e) {
      return ResultWithModelWrapper.exception(LocalException(
          this, EnumGuilty.device, ReadyDataUtility.unknown, e.toString()));
    }
  }
}

enum EnumDataForMainVM { isLoading, exception, success }

final class DataForMainVM extends BaseDataForNamed<EnumDataForMainVM> {
  IPAddress iPAddress;

  DataForMainVM(super.isLoading, this.iPAddress);

  @override
  EnumDataForMainVM get getEnumDataForNamed {
    if (isLoading) {
      return EnumDataForMainVM.isLoading;
    }
    if (exceptionController.isWhereNotEqualsNullParameterException()) {
      return EnumDataForMainVM.exception;
    }
    return EnumDataForMainVM.success;
  }

  @override
  String toString() {
    return "DataForMainVM(isLoading: $isLoading, "
        "exceptionController: $exceptionController, "
        "iPAddress: $iPAddress)";
  }
}

final class MainVM {
  // ModelWrapperRepository
  final _iPAddressWrapperRepository = FactoryModelWrapperRepositoryUtility
      .getIPAddressWrapperRepositoryFromNamedHttpClientService(
          TimeoutHttpClientService.instance);

  // TempCacheProvider
  final _tempCacheProvider = TempCacheProvider();

  // NamedUtility

  // NamedStreamWState
  late final BaseNamedStreamWState<DataForMainVM> _namedStreamWState;

  MainVM() {
    _namedStreamWState = DefaultStreamWState<DataForMainVM>(
        DataForMainVM(true, const IPAddress("")));
  }

  Future<void> init() async {
    _namedStreamWState.listenStreamDataForNamedFromCallback((event) {
      _build();
    });
    final firstRequest = await _firstRequest();
    debugPrint("MainVM: $firstRequest");
    _namedStreamWState.notifyStreamDataForNamed();
  }

  void dispose() {
    _iPAddressWrapperRepository.dispose();
    _tempCacheProvider.dispose([]);
    _namedStreamWState.dispose();
  }

  void _build() {
    final dataWNamed = _namedStreamWState.getDataForNamed;
    switch (dataWNamed.getEnumDataForNamed) {
      case EnumDataForMainVM.isLoading:
        debugPrint("Build: IsLoading");
        break;
      case EnumDataForMainVM.exception:
        debugPrint(
            "Build: Exception(${dataWNamed.exceptionController.getKeyParameterException})");
        break;
      case EnumDataForMainVM.success:
        debugPrint("Build: Success(${dataWNamed.iPAddress})");
        break;
    }
  }

  Future<String> _firstRequest() async {
    final getIPAddressParameterNamedHttpClientService =
        await _iPAddressWrapperRepository
            .getIPAddressParameterNamedHttpClientService();
    if (getIPAddressParameterNamedHttpClientService.exceptionController
        .isWhereNotEqualsNullParameterException()) {
      return _firstQQFirstRequestQQGetIPAddressParameterNamedHttpClientService(
          getIPAddressParameterNamedHttpClientService.exceptionController);
    }
    _namedStreamWState.getDataForNamed.isLoading = false;
    _namedStreamWState.getDataForNamed.iPAddress =
        getIPAddressParameterNamedHttpClientService.modelWrapper!.createModel();
    return ReadyDataUtility.success;
  }

  Future<String>
      _firstQQFirstRequestQQGetIPAddressParameterNamedHttpClientService(
          ExceptionController exceptionController) async {
    _namedStreamWState.getDataForNamed.isLoading = false;
    _namedStreamWState.getDataForNamed.exceptionController =
        exceptionController;
    return exceptionController.getKeyParameterException;
  }
}

Future<void> main() async {
  final mainVM = MainVM();
  await mainVM.init();
  mainVM.dispose();
}

输出结果

期望输出:

MainVM: success
Build: Success(IPAddress(ip: ${your_ip}))

Process finished with exit code 0

或者

===start_to_trace_exception===

WhereHappenedException(Class) --> ${WhereHappenedException(Class)}
NameException(Class) --> ${NameException(Class)}
toString() --> ${toString()}

===end_to_trace_exception===

MainVM: ${getKeyParameterException}
Build: Exception(${getKeyParameterException})

Process finished with exit code 0

更多关于Flutter MVVM架构插件library_architecture_mvvm_modify的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter MVVM架构插件library_architecture_mvvm_modify的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是如何在Flutter项目中使用library_architecture_mvvm_modify插件来构建MVVM架构的一个简单示例。请注意,由于library_architecture_mvvm_modify这个库名称看起来是一个假设的或者特定定制的库(Flutter社区中并没有一个广泛认知的同名库),我将基于一般MVVM架构的原理和Flutter的常规实践来展示一个示例代码结构。

1. 项目结构

首先,我们定义一个基本的项目结构来支持MVVM架构:

my_flutter_app/
├── lib/
│   ├── data/       # 数据层
│   │   ├── repository/
│   │   │   └── user_repository.dart
│   ├── model/      # 数据模型层
│   │   └── user.dart
│   ├── view/       # 界面层
│   │   └── user_screen.dart
│   ├── viewmodel/  # ViewModel层
│   │   └── user_viewmodel.dart
│   └── main.dart

2. 数据模型 (Model)

model文件夹中定义数据模型,例如一个User类:

// lib/model/user.dart
class User {
  final String name;
  final int age;

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

  factory User.fromJson(Map<String, dynamic> json) {
    return User(
      name: json['name'] as String,
      age: json['age'] as int,
    );
  }

  Map<String, dynamic> toJson() {
    return {
      'name': name,
      'age': age,
    };
  }
}

3. 数据访问层 (Data Layer)

data/repository文件夹中定义一个仓库类,用于处理数据访问逻辑:

// lib/data/repository/user_repository.dart
import 'package:flutter/material.dart';
import 'package:my_flutter_app/model/user.dart';

class UserRepository {
  // 模拟从API获取用户数据
  Future<User> fetchUser() async {
    await Future.delayed(Duration(seconds: 2)); // 模拟网络延迟
    return User(name: 'John Doe', age: 30);
  }
}

4. ViewModel

viewmodel文件夹中定义一个ViewModel类,它负责处理业务逻辑并管理UI状态:

// lib/viewmodel/user_viewmodel.dart
import 'package:flutter/material.dart';
import 'package:my_flutter_app/data/repository/user_repository.dart';
import 'package:my_flutter_app/model/user.dart';

class UserViewModel with ChangeNotifier {
  User? _user;
  User? get user => _user;
  bool isLoading = false;
  bool hasError = false;
  String? errorMessage;

  final UserRepository _userRepository = UserRepository();

  void fetchUser() async {
    isLoading = true;
    hasError = false;
    errorMessage = null;

    try {
      _user = await _userRepository.fetchUser();
    } catch (_) {
      hasError = true;
      errorMessage = 'Failed to fetch user data.';
    } finally {
      isLoading = false;
      notifyListeners();
    }
  }
}

5. 界面层 (View)

view文件夹中定义一个Widget,它使用ViewModel来展示数据并处理用户交互:

// lib/view/user_screen.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:my_flutter_app/viewmodel/user_viewmodel.dart';

class UserScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final userViewModel = Provider.of<UserViewModel>(context);

    return Scaffold(
      appBar: AppBar(title: Text('User Info')),
      body: Center(
        child: userViewModel.isLoading
            ? CircularProgressIndicator()
            : userViewModel.hasError
                ? Text(userViewModel.errorMessage!)
                : Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      Text('Name: ${userViewModel.user!.name}'),
                      Text('Age: ${userViewModel.user!.age}'),
                    ],
                  ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: userViewModel.fetchUser,
        tooltip: 'Fetch User',
        child: Icon(Icons.refresh),
      ),
    );
  }
}

6. 主程序入口 (Main Entry)

main.dart中设置Provider,并指定根Widget:

// lib/main.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:my_flutter_app/viewmodel/user_viewmodel.dart';
import 'package:my_flutter_app/view/user_screen.dart';

void main() {
  runApp(
    MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (_) => UserViewModel()),
      ],
      child: MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter MVVM Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: UserScreen(),
    );
  }
}

总结

以上代码展示了如何在Flutter中使用MVVM架构模式,通过分层的方式将应用逻辑分离到不同的组件中。虽然library_architecture_mvvm_modify这个库可能不存在,但基于上述示例,你可以很容易地扩展或修改以满足特定需求。希望这能帮助你理解和实现Flutter中的MVVM架构。

回到顶部