分享我在flutter中使用的MVVM框架 - 2

前言

嗨,各位好,天气逐渐转凉,记得多保重身体。

在上一篇中,介绍了我自己想的MVVM框架,只是在实务上有时候并不是那么的好理解,因此在我研究许久以后,写了第二个版本,这次也加上如何call api,让各位可以看看成果!

由於這次更改的地方著重在viewModel以及api的部分,因此這篇只會介紹這兩個地方。

代碼解釋

api.dart

dart 复制代码
class CallApi{}

首先我会建立一个api Basic,专门拿来给所有的api提供一个通用的接口,方便管理。

dart 复制代码
    if (kDebugMode) {
      // 在调试模式下使用的基本 URL
      return 'https://api.open-meteo.com';
    } else {
      // 在 release 模式下使用的基本 URL
      return 'https://api.open-meteo.com';
    }
  }

在上面我会先写一个 getBaseUrl() ,这个主要是在调试的过程中,避免掉每一次修改baseUrl会造成部署上的错误

dart 复制代码
 //共用 API 請求處理邏輯
  Future<Response> handleApiCall(
    Future<Response> Function() apiCall, {
    Function()? onSuccess,
    Function()? onError,
  }) async {
    try {
      // onSuccess?.call();
      final response = await apiCall();
      onSuccess?.call();
      return response;
    } catch (e) {
      print('API 呼叫錯誤: $e');
      onError?.call();
      rethrow;
    }
  }

再来我会建立一个handle的function,提供每一个api能够使用get/post与成功或错误时会执行的function

dart 复制代码
Future<Response> get(String endpoint, Map<String, dynamic> queryParams,
    Map<String, String> header) async {
  final String baseUrl = getBaseUrl();
  print('GET 請求: $endpoint');
  Uri uri =
      Uri.parse(baseUrl + endpoint).replace(queryParameters: queryParams);
  return _handleRequest(() => http.get(uri, headers: header));
}

Future<Response> post(
    String endpoint, dynamic reqBody, Map<String, String> header) async {
  final String baseUrl = getBaseUrl();
  print('POST 請求: $endpoint');
  Uri uri = Uri.parse(baseUrl + endpoint);
  final body = jsonEncode(reqBody);
  return _handleRequest(() => http.post(uri, headers: header, body: body));
}

Future<Response> _handleRequest(
    Future<http.Response> Function() request) async {
  try {
    final httpResponse = await request();
    log('HTTP 回傳狀態: ${httpResponse.statusCode}');

    if (httpResponse.statusCode == 200) {
      return Response.fromJson(jsonDecode(httpResponse.body));
    } else {
      throw Exception('Server Error: ${httpResponse.statusCode}');
    }
  } catch (e) {
    log('HTTP 請求失敗: $e');
    rethrow;
  }
}

最后我会写get与post的function,并且把get与post后,回传回来的request去做一个判断,并且把资料回传回去

service/home.dart

再来是建立一个get的function,用于get这次的资料:天气资料。

dart 复制代码
class HomeService {
  final CallApi api;

  HomeService() : api = CallApi(); // 直接初始化

  Future<HourlyWeather> hourlyWeather(
      {required Map<String, dynamic> queryParams,
      required Map<String, String> header}) async {
    final res = await api.handleApiCall(
      () => api.get("/v1/forecast", queryParams, header),
      onError: () => print("取得失敗"),
    );

    final hourlyWeather =
        HourlyWeather.fromJson(res.hourly as Map<String, dynamic>);

    return hourlyWeather;
  }
}

这里会需要传入query、header,但因为这次不需要header,因此我会在更原头的地方传空字串。

会把service当成class写,最主要的原因是因为要整合,也很方便扩增需求

(毕竟面对客户,增加需求是常有的事情)

这样也单独对service做单元测试,在做call 金流方面的api时,单元测试是很重要的!

在能够成功回传资料以后,这边的功能也就完成啦。

viewModel/home.dart

dart 复制代码
class HomeViewModel extends ChangeNotifier {
  final HomeService homeService = HomeService();
  HourlyWeather hourlyWeather = HourlyWeather();
  Future<HourlyWeather?>? weatherFuture;

  Future<void> callGetWeather({
    required Map<String, String> queryParams,
    required Map<String, String> header,
  }) async {
    weatherFuture = homeService
        .hourlyWeather(queryParams: queryParams, header: header)
        .then((data) {
      hourlyWeather = data;
      notifyListeners();
    }).catchError((e) => throw e);
  }
}

在这里会建立一个service,再建立一个model,用于储存资料并显示在画面上。这里的query与header仍然是要传入的,因此可以让所有变数集中在一个地方,也是方便管理!

page/home.dart

dart 复制代码
      homeViewModel.callGetWeather(queryParams: {
        "latitude": "52.52",
        "longitude": "13.41",
        "hourly": "temperature_2m",
        "models": "cma_grapes_global",
      }, header: globalViewModel.headers);
    });

这里会把需要的query与header给api,并且层层传下去,确保了变数的一致性。

这里的globalViewModel.plantHeaders如下:

dart 复制代码
  Map<String, String> get headers {
    return headers().setHeader("", token: "");
  }

会这样子写也是为了要确保header一致,因此使用一个通用的header。

最后我们在view中,用FutureBuilder把资料显示出来,这样就完成啦!

dart 复制代码
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
        create: (_) => homeViewModel,
        child: Scaffold(
          appBar: AppBar(
            backgroundColor: Theme.of(context).colorScheme.inversePrimary,
            title: const Text("首頁"),
            centerTitle: true,
          ),
          body: Center(
            child: Consumer<HomeViewModel>(
              builder: (context, viewModel, child) {
                return FutureBuilder<HourlyWeather?>(
                  future: viewModel.weatherFuture,
                  builder: (context, snapshot) {
                    if (snapshot.connectionState == ConnectionState.waiting ||
                        snapshot.connectionState == ConnectionState.none) {
                      return const CircularProgressIndicator();
                    }
                    if (snapshot.hasError) {
                      return Text('Error: ${snapshot.error}');
                    }

                    log(viewModel.hourlyWeather.time!.length.toString());
                    return ListView.builder(
                      itemCount: viewModel.hourlyWeather.time!.length,
                      itemBuilder: (context, index) {
                        return ListTile(
                          title: Text(viewModel.hourlyWeather.time![index]),
                          subtitle: Text(viewModel
                              .hourlyWeather.temperature_2m![index]
                              .toString()),
                        );
                      },
                    );
                  },
                );
              },
            ),
          ),
        ));
  }

演示效果画面如下:

有点丑,因为想说是范例,因此便着重在介绍架构而非画面😝

结语

那这就是这次对于我的MVVM中,我所改进的地方,以及介绍了如何使用Api的部分!

这次的改进主要也是受到了前端网页的影响,想要把一些东西分得更开,除了方便做单元测试以外,也能让后进能够快速地看懂代码的构造,不会因为viewModel把所有东西挤在一起而看得更久。

感谢看到这里的你,希望今后的你也是顺顺利利的喔!

相关推荐
小满zs4 分钟前
React第十二章(useSyncExternalStore)
前端·javascript·react.js
Mrs_Lupin6 分钟前
React核心思维模型(一)
前端·react.js·前端框架
你不讲 wood19 分钟前
预览 PDF 文档
开发语言·前端·javascript·pdf·html·node·文件预览
我就说好玩39 分钟前
基于echarts、php、Mysql开发的数据可视化大屏
前端·后端·信息可视化·echarts
2301_7891695440 分钟前
ai说ajax
前端·javascript·ajax
小白讲前端1 小时前
炫酷的登录框!(附源码)
前端·javascript·css·html
二川bro1 小时前
CSS兼容处理
前端·css
XiaoYu20021 小时前
31.JS高级-包管理工具详解
前端·javascript·程序员
终将超越过去2 小时前
Element Plus在Vue3的安装
前端·javascript·vue.js
xu_duo_i2 小时前
Vxe UI 表格行编辑(默认不显示编辑框,点击后可编辑)
前端·vue.js·ui·vxe