Flutter mvvm簡單實戰

wz_app發表於2021-03-19

內容指導

本章不是一個初學者的話題,如果您連基礎的介面呼叫,ui介面都不會,推薦先去學習flutter基礎再來看這篇部落格

架構簡介

  • view層 顯示view
  • model層 請求處理http操作
  • viewmodel儲存狀態,處理業務邏輯,充當view跟model通訊的橋樑
  • util儲存工具類,比如dio封裝

專案簡介

沒有封裝dio等 只是實現了mvvm登陸功能,也許有的地方不正確,希望各位大佬指出 如果對您有所幫助,麻煩動動小手點個贊

專案建立

flutter create mvvm_demo

複製程式碼

專案分析

1.由於我們viewmodel負責管理資料,以及負責view跟model層通訊,所以這裡我們用proivder狀態管理 引入provider

provider: ^4.3.2+3
複製程式碼

2.專案api請求呼叫,我這裡選擇dio,所以還需要引入dio

dio: 3.0.10
複製程式碼

由於要實現登陸功能,所以我在老專案上找了一個登陸介面,這裡不放出這個介面,大家自己解決介面問題 首先確認api需要的引數

  • userName
  • passWord

我的api只需要兩個引數

3.由於我們viewmodel來處理業務邏輯,所以我們viewmodel跳轉介面或彈框的時候,怎麼獲取到當前的context? 我們採用全域性navigatorKey

修改main.dart

final GlobalKey<NavigatorState> navigatorKey = new GlobalKey<NavigatorState>();

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      navigatorKey: navigatorKey,有了這個,在其他類就可以呼叫navigatorKey來獲取context了
//省略
複製程式碼

搭建專案結構

api以及引入全部完成後,我們要搭建專案結構,建立以下幾個包

  • model
  • viewmodel
  • view
  • util

前面三個不用介紹,第四個util做dio封裝方法,我們這裡只簡單封裝一下

建立DioUtil

在util下建立 dio_util.dart 檔案

import 'package:dio/dio.dart';

//程式碼很簡單,需要傳一個介面地址,以及引數
dynamic post(String url, Map map) async {
  Response response = await Dio().post(url, data: map);
  return response.data;
}

複製程式碼

實際專案會對dio封裝,由於是demo不做演示

建立model

在 model下建立 login_model.dart 檔案 model只有http請求程式碼

import 'package:mvvm_demo/util/dio_util.dart';

class LoginModel {
  //提供api請求方法,並把介面返回資訊當作返回值
  dynamic login(Map map) async {
    return await post(
        "http://106.38.32.194:86/OrderTrackerService.asmx/Login", map);
  }
}

複製程式碼

建立viewmodel

在 viewmodel下建立 login_viewmodel.dart 檔案 viewmodel只儲存狀態,以及處理業務邏輯,裡邊沒有widget更沒有http請求程式碼 首先我們的後臺需要賬號密碼兩個引數,那麼我們介面肯定有兩個輸入框,我們這裡輸入框的值用controller來接收

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:mvvm_demo/main.dart';
import 'package:mvvm_demo/model/login_model.dart';
import 'package:mvvm_demo/view/menu.dart';

class LoginViewmodel extends ChangeNotifier {
  //model物件,用於呼叫api請求方法
  LoginModel _model = LoginModel();
  //賬號輸入框controller
  TextEditingController _user = TextEditingController();
  //密碼輸入框controller
  TextEditingController _pass = TextEditingController();
  dynamic _result = ""; //登陸返回的錯誤資訊,如果正確登陸,則為空

  下邊get set方法不做介紹

  TextEditingController get user {
    return _user;
  }

  TextEditingController get pass {
    return _pass;
  }



  void setResult(dynamic data) {
    _result = data;
    notifyListeners();//如果有錯誤資訊,則重新整理所有觀察者
    //比如一個text用了這個值,則視為觀察者
    //必須正確使用provider狀態管理,才可以監聽到值,下一步驟會告訴大家怎麼配置provider
  }

  dynamic get result {
    return _result;
  }

  void login() async {//給view層提供登陸方法
    dynamic result = await _model.login({//呼叫model層的api請求方法,並把引數當作map傳給model,提供給dio請求
      "userName": _user.text,
      "passWord": _pass.text,
    });
    print(result.toString());//返回值
    if (result.toString().substring(0, 5) == "false") {
      //如果登陸失敗,則在介面提示錯誤資訊
      //我的如果登陸失敗返回格式為 **false錯誤資訊**
      //所以我的錯誤資訊要從第六位擷取
      setResult(result.toString().substring(5));
    } else {
      setResult(""); //登陸成功,清空錯誤提示資訊
      //下邊程式碼可以獲取到我們剛才設定的全域性navigatorKey的context,來實現介面跳轉
      Navigator.of(navigatorKey.currentContext).push(
        CupertinoPageRoute(
          builder: (context) {
            return Menu();
          },
        ),
      ); //跳轉介面
    }
  }
}

複製程式碼

配置provider狀態管理

修改 main.dart


void main() {
  runApp(MyApp());
}
複製程式碼

修改為


void main() {
  runApp(MultiProvider(
    providers: [
      ChangeNotifierProvider(create: (context) => LoginViewmodel()),
      //多個介面請在下方新增多個viewmodel
    ],
    child: MyApp(),
  ));
}
複製程式碼

建立view

在 view下建立 login.dart 檔案

import 'package:flutter/material.dart';
import 'package:mvvm_demo/viewmodel/login_viewmodel.dart';
import 'package:provider/provider.dart';

class Login extends StatefulWidget {
  @override
  _LoginState createState() => _LoginState();
}

class _LoginState extends State<Login> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("登陸"),
      ),
      body: Padding(
        padding: EdgeInsets.all(10),
        child: Column(
          children: [
            TextField(
              controller: Provider.of<LoginViewmodel>(context).user,//獲取viewmodel管理的controller狀態
              decoration: InputDecoration(
                labelText: "賬號",
                prefixIcon: Icon(Icons.person),
              ),
            ),
            SizedBox(height: 16),
            TextField(
              controller: Provider.of<LoginViewmodel>(context).pass,//獲取viewmodel管理的controller狀態
              decoration: InputDecoration(
                labelText: "密碼",
                prefixIcon: Icon(Icons.lock),
              ),
              obscureText: true,
            ),
            SizedBox(height: 16),
            Container(
              width: double.infinity,
              child: RaisedButton(
                onPressed: context.read<LoginViewmodel>().login,//呼叫viewmodel層的登陸方法
                color: Colors.blue,
                child: Text(
                  "登陸",
                  style: TextStyle(color: Colors.white),
                ),
              ),
            ),
            SizedBox(height: 16),
            Expanded(
              child:
                  Text(Provider.of<LoginViewmodel>(context).result.toString()),////獲取viewmodel管理的result錯誤資訊
            ),
          ],
        ),
      ),
    );
  }
}

複製程式碼

效果展示

yvipdx7n77.gif

總結

這樣是不是程式碼非常整潔? ui有問題就在view層改,並且view層只有ui程式碼 業務有問題就在viewmodel層改 這樣極大增加了專案維護的便捷性

如果本人有什麼地方理解錯誤,還望大家評論或私聊告訴我,一起學習進步

相關文章