前言
嗨,各位好,天气逐渐转凉,记得多保重身体。
在上一篇中,介绍了我自己想的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把所有东西挤在一起而看得更久。
感谢看到这里的你,希望今后的你也是顺顺利利的喔!