在 GetX 中调用 API 的正确方法是什么? - flutter - SO中文参考 (2024)

使用 Getx 和 MVVM 发布 API

POST API:https://reqres.in/api/login

电子邮件:[电子邮件受保护]

密码:cityslicka

第一步:创建抽象类BaseApiService

abstract class BaseApiService { Future<dynamic> getApi(String url); Future<dynamic> postApi(dynamic data, String url);}

第2步:创建类AppExceptions并实现Exception

class AppExceptions implements Exception { final _message; final _prefix; AppExceptions([this._message, this._prefix]); String toString() { return '$_prefix$_message'; }}

第 3 步:创建 InternetException 类并扩展 AppExceptions

class InternetException extends AppExceptions { InternetException([String? message]) : super(message, "No Internet");}

第 4 步:创建类 RequestTimeOut 并扩展 AppExceptions

class RequestTimeOut extends AppExceptions { RequestTimeOut([String? message]) : super(message, 'Request Time out');}

第 5 步:创建 ServerException 类并扩展 AppExceptions

class ServerException extends AppExceptions { ServerException([String? message]) : super(message, 'Internal server error');}

第 6 步:创建类 InvalidUrlException 并扩展 AppExceptions

class InvalidUrlException extends AppExceptions { InvalidUrlException([String? message]) : super(message, 'Invalid Url');}

第 7 步:创建类 FetchDataException 并扩展 AppExceptions

class FetchDataException extends AppExceptions { FetchDataException([String? message]) : super(message, '');}

第8步:创建NetworkApiService类并扩展BaseApiService

class NetworkApiService extends BaseApiService { @override Future<dynamic> getApi(String url) async { if (kDebugMode) { print(url); } dynamic responseJson; try { final response = await http.get(Uri.parse(url)).timeout(const Duration(minutes: 3)); responseJson = returnResponse(response); } on SocketException { throw InternetException(""); } on RequestTimeOut { throw RequestTimeOut(""); } print(responseJson); return responseJson; } @override Future<dynamic> postApi(var data, String url) async { if (kDebugMode) { print(url); print(data); } dynamic responseJson; try { final response = await http.post(Uri.parse(url), body: data).timeout(const Duration(minutes: 3)); responseJson = returnResponse(response); } on SocketException { throw InternetException(""); } on RequestTimeOut { throw RequestTimeOut(""); } if (kDebugMode) { print(responseJson); } return responseJson; } dynamic returnResponse(http.Response response) { switch (response.statusCode) { case 200: dynamic responseJson = jsonDecode(response.body); return responseJson; case 400: throw FetchDataException("Bad Request: ${response.body}"); default: throw FetchDataException(AppString.errorInServer + response.statusCode.toString()); } }}

第9步:创建类AppUrl

class AppUrl { static const String baseUrl = 'https://reqres.in' ; static const String loginApi = '$baseUrl/api/login' ;}

第10步:创建类LoginRepository

class LoginRepository { final _apiService = NetworkApiService(); Future<dynamic> loginApi(var data) async { try { dynamic response = await _apiService.postApi(data, AppUrl.loginApi); return response; } catch (error) { rethrow; } }}

第11步:创建UserModel类

class UserModel { String? token; bool? isLogin ; UserModel({this.token , this.isLogin}); UserModel.fromJson(Map<String, dynamic> json) { token = json['token']; isLogin = json['isLogin']; } Map<String, dynamic> toJson() { final Map<String, dynamic> data = new Map<String, dynamic>(); data['token'] = this.token; data['isLogin'] = this.token; return data; }}

第 12 步:创建 UserPreference 类

class UserPreference { Future<bool> saveUser(UserModel responseModel) async { SharedPreferences sp = await SharedPreferences.getInstance(); sp.setString('token', responseModel.token.toString()); sp.setBool('isLogin', responseModel.isLogin!); return true; } Future<UserModel> getUser() async { SharedPreferences sp = await SharedPreferences.getInstance(); String? token = sp.getString('token'); bool? islogin = sp.getBool('isLogin'); return UserModel(token: token, isLogin: islogin); } Future<bool> removeUser() async { SharedPreferences sp = await SharedPreferences.getInstance(); sp.clear(); return true; }}

第13步:制作类Utils

class Utils { static void fieldFocusNode(BuildContext context, FocusNode current, FocusNode nextFocus) { current.unfocus(); FocusScope.of(context).requestFocus(nextFocus); } static toastMessage(String message) { Fluttertoast.showToast( msg: message, backgroundColor: AppColors.blackColor, textColor: AppColors.whiteColor, gravity: ToastGravity.BOTTOM, toastLength: Toast.LENGTH_LONG); } static snackBar(String title, String message, Color backgroundColor, Color colorText) { Get.snackbar(title, message, backgroundColor: backgroundColor, colorText: colorText); }}

第14步:创建类LoginViewModel并扩展GetxController

class LoginViewModel extends GetxController { final _api = LoginRepository(); UserPreference userPreference = UserPreference(); final emailController = TextEditingController().obs; final passwordController = TextEditingController().obs; final emailFocusNode = FocusNode().obs; final passwordFocusNode = FocusNode().obs; RxBool loading = false.obs; void loginApi() { loading.value = true; Map data = {"email": emailController.value.text, "password": passwordController.value.text}; _api.loginApi(data).then((value) { loading.value = false; if (value["error"] == "User not found") { Utils.snackBar("Login", value["error"], AppColors.redLightColor, AppColors.redColor); } else { UserModel userModel = UserModel(token: value["token"], isLogin: true); userPreference.saveUser(userModel).then((value) { Get.delete<LoginViewModel>(); Get.toNamed(RouteName.homeScreen)!.then((value) {}); Utils.snackBar("Login", "Login successfully", AppColors.greenLightColor, AppColors.greenColor); }).onError((error, stackTrace) { Utils.snackBar("Error", "Failed to save user data", AppColors.redLightColor, AppColors.redColor); }); } }).onError((error, stackTrace) { loading.value = false; if (error is FetchDataException && error.toString().contains("Bad Request")) { Utils.snackBar("Login", "User not found or bad request", AppColors.redLightColor, AppColors.redColor); } else { Utils.snackBar("Error", error.toString(), AppColors.redLightColor, AppColors.redColor); } }); }}

第15步:创建类LoginView并扩展StatelessWidget

class LoginView extends StatelessWidget { LoginView({super.key}); final loginVM = Get.put(LoginViewModel()); final _formKey = GlobalKey<FormState>(); final RxBool _obscureText = true.obs; @override Widget build(BuildContext context) { var height = MediaQuery.of(context).size.height * 1.0; var width = MediaQuery.of(context).size.width * 1.0; return Scaffold( body: SafeArea( top: true, bottom: true, child: Center( child: Container( height: height, width: width, child: Padding( padding: EdgeInsets.all(20.0), child: Column( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, children: [ CustomText(text: AppString.loginText, fontSize: 30.0, color: AppColors.primeryColor, fontWeight: FontWeight.bold), SizedBox( height: 35, ), Form( key: _formKey, child: Column( children: [ CustomTextField( keyboardType: TextInputType.emailAddress, prefixIcon: Icons.email, labelText: CustomText( text: AppString.userNameLable, color: AppColors.primeryColor, ), hintText: CustomText( text: AppString.userNameHint, color: AppColors.primeryLightColor, ), controller: loginVM.emailController.value, focusNode: loginVM.emailFocusNode.value, validator: (value) { if (value!.isEmpty) { Utils.snackBar("Email", "Enter email", AppColors.redLightColor, AppColors.redColor); } else if (!RegExp(r'^[\w-]+(\.[\w-]+)*@([\w-]+\.)+[a-zA-Z]{2,7}$').hasMatch(value)) { return 'emailVadilate'; } return null; }, onFieldSubmitted: (value) { Utils.fieldFocusNodeChange(context, loginVM.emailFocusNode.value, loginVM.passwordFocusNode.value); }, ), SizedBox( height: 10, ), Obx( () => CustomTextField( labelText: CustomText( text: AppString.passwordLable, color: AppColors.primeryColor, ), hintText: CustomText( text: AppString.passwordHint, color: AppColors.primeryLightColor, ), controller: loginVM.passwordController.value, focusNode: loginVM.passwordFocusNode.value, validator: (value) { if (value!.isEmpty) { Utils.snackBar("Password", "Enter Password", AppColors.redLightColor, AppColors.redColor); } }, keyboardType: TextInputType.text, prefixIcon: Icons.lock, suffixIcon: _obscureText.value ? Icons.visibility : Icons.visibility_off, onSuffixIconPressed: () { _obscureText.value = !_obscureText.value; }, obscureText: _obscureText, obscuringCharacter: "*", ), ), ], )), SizedBox( height: 25, ), Obx( () => CustomButton( title: CustomText( text: AppString.loginButton, color: AppColors.whiteColor, fontWeight: FontWeight.bold, ), loading: loginVM.loading.value, onPressed: () { if (_formKey.currentState!.validate()) { loginVM.loginApi(); } }), ) ], ))), ))); }}

第 16 步:创建 AppColors 类

class AppColors { static const Color hintTextColor = Color(0xB2A4A4A4); static const Color whiteColor = Color(0xffffffff); static const Color blackColor = Color(0xff000000); static const Color primeryColor = Color(0xff7f65cb); static const Color primeryLightColor = Color(0xffc5b2fd); static const Color redColor = Color(0xffd42020); static const Color redLightColor = Color(0xfff5dfdf); static const Color greenLightColor = Color(0xffe0f1dd); static const Color greenColor = Color(0xff0e5900);}
在 GetX 中调用 API 的正确方法是什么? - flutter - SO中文参考 (2024)
Top Articles
Latest Posts
Article information

Author: Prof. Nancy Dach

Last Updated:

Views: 5859

Rating: 4.7 / 5 (77 voted)

Reviews: 92% of readers found this page helpful

Author information

Name: Prof. Nancy Dach

Birthday: 1993-08-23

Address: 569 Waelchi Ports, South Blainebury, LA 11589

Phone: +9958996486049

Job: Sales Manager

Hobby: Web surfing, Scuba diving, Mountaineering, Writing, Sailing, Dance, Blacksmithing

Introduction: My name is Prof. Nancy Dach, I am a lively, joyous, courageous, lovely, tender, charming, open person who loves writing and wants to share my knowledge and understanding with you.