Flutter實戰5 -- 天氣查詢APP重構之狀態管理(ScopedModel)

小德_koude發表於2019-01-15

0x00 前言

前面四篇文章:

  1. Flutter實戰1 --- 寫一個天氣查詢的APP
  2. Flutter實戰2 --- 寫一個天氣查詢的APP
  3. FFlutter實戰3 --- PC上執行Flutter APP
  4. Flutter實戰4 -- 天氣查詢APP重構之狀態管理(InheritedWidget)

在第4篇文章中,為了方便管理狀態,我們介紹了InheritedWidget,今天介紹ScopedModel,這是一個封裝的InheritedWidget的庫,使用起來更方便。

0x01 程式碼

本篇文章所涉及的程式碼:

github.com/google/flut…

分支:scoped_model

0x02 ScopedModel

ScopedModel:github.com/brianegan/s…

可以方便的將model從父Widget傳遞到它的後代。而且還會在model更新時重建使用該model的所有子項。該庫是從Fuchsia程式碼庫中提取的。

0x03 ScopedModel引入

pubspec.yaml里加入:

scoped_model: ^1.0.1
複製程式碼

然後執行flutter packages get

0x04 ScopedModel使用

ScopedModel使用,主要是下面三個類:

  1. Model

    你要繼承Model這個類,實現自己的Models,這個Module類裡,持有相關的資料,及實現一些業務邏輯,可以監聽Model裡的資料變化

  2. ScopedModel

    ScopedModel是一個Widget,確定Model的使用範圍:像使用InheritedWidget一樣,你需要用ScopedModel包其他Widget,而且Model也是包在ScopedModel裡的,這樣Model就可以沿著Widget樹向下傳遞。

  3. ScopedModelDescendant ScopedModelDescendant也是一個Widget,在子Widget獲取Model使用:使用ScopedModelDescendant,是為了在子Widget裡找到相應的Model。只要是Model發生更改,它就會自動重建。

0x05 重構 -- 實現Model

新增了兩個Model,分別是:

  1. CityModel

作用:獲取城市列表

import 'dart:convert';

import 'package:flutter/widgets.dart';
import 'package:gdg_weather/page/city/CityData.dart';
import 'package:scoped_model/scoped_model.dart';
import 'package:http/http.dart' as http;


class CityModel extends Model{

  List<CityData> cityList = new List<CityData>();

  CityModel(){

  }
  
  static CityModel of(BuildContext context) => ScopedModel.of<CityModel>(context,rebuildOnChange: true);

  //獲取城市列表的方法
  void getCityList() async {
    final response = await http.get('https://search.heweather.net/top?group=cn&key=ebb698e9bb6844199e6fd23cbb9a77c5');

    List<CityData> list = new List<CityData>();

    if(response.statusCode == 200){
      //解析資料
      Map<String,dynamic> result = json.decode(response.body);
      for(dynamic data in result['HeWeather6'][0]['basic']){
        CityData cityData = CityData(data['location']);
        list.add(cityData);
      }
    }        

    cityList = list;   
    
    //這裡一定要,資料變化,通知widget重新整理
    notifyListeners();
  }
}
複製程式碼
  1. WeatherModel
import 'dart:convert';

import 'package:gdg_weather/page/weather/WeatherData.dart';
import 'package:scoped_model/scoped_model.dart';
import 'package:http/http.dart' as http;

class WeatherModel extends Model{
  WeatherData weather = WeatherData.empty();

  void fetchWeather(String cityName) async{
    final response = await http.get('https://free-api.heweather.com/s6/weather/now?location='+cityName+'&key=ebb698e9bb6844199e6fd23cbb9a77c5');
    if(response.statusCode == 200){
      weather = WeatherData.fromJson(json.decode(response.body));
    }else{
      weather = WeatherData.empty();
    }

    notifyListeners();
  }
}
複製程式碼

0x06 重構 -- 原有widget重構

Model實現完後,接下來就是對原有widget重構,

第一個步是新增ScopedModel,確定Model的使用範圍:

ScopedModel<CityModel>(
      model: CityModel(),
      child: ...
    );  
複製程式碼

第二步是使用ScopedModelDescendant,在子Widget獲取Model使用,所以重構如下:

ScopedModelDescendant<CityModel>(
        builder: (context,child,model){
          //使用model
          model....
        }
    )    
複製程式碼
  1. CityWidget
class CityState extends State<CityWidget>{

  CityState(){
  }

  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return ScopedModel<CityModel>(
      model: CityModel(),
      child: ScopedModelDescendant<CityModel>(
        builder: (context,child,model){
          model.getCityList();
          return ListView.builder(
                    itemCount: model.cityList.length,
                    itemBuilder: (context,index){
                        return ListTile(
                            title: GestureDetector(
                              child:  Text(model.cityList[index].cityName),
                                  onTap:(){
                                    Navigator.push(
                                      context,
                                      MaterialPageRoute(builder: (context) => WeatherWidget(model.cityList[index].cityName))
                                    );
                                  },
                              ),
                          );
                      }
                  );
        },
      )
    );
  }

}
複製程式碼
  1. WeatherWidget
class WeatherState extends State<WeatherWidget>{

  String cityName;


  WeatherState(String cityName){
    this.cityName = cityName;
  }



  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Scaffold(
      body: Stack(
        fit: StackFit.expand,
        children: <Widget>[
          Image.asset("images/weather_bg.jpg",fit: BoxFit.fitHeight,),
          Column(
            mainAxisAlignment: MainAxisAlignment.start,
            crossAxisAlignment: CrossAxisAlignment.center,
            children: <Widget>[
              Container(
                width: double.infinity,
                margin: EdgeInsets.only(top: 40.0),
                child: new Text(
                  this.cityName,
                  textAlign: TextAlign.center,
                  style: new TextStyle(
                    color: Colors.white,
                    fontSize: 30.0,
                  ),
                ),
              ),
              Container(
                width: double.infinity,
                margin: EdgeInsets.only(top: 100.0),
                child: ScopedModel<WeatherModel>(
                  model: WeatherModel(),
                  child: ScopedModelDescendant<WeatherModel>(
                    builder: (context,child,model){
                      model.fetchWeather(this.cityName);
                      return Column(
                              children: <Widget>[
                                Text(
                                  model.weather?.tmp,
                                  style: new TextStyle(
                                    color: Colors.white,
                                    fontSize: 80.0
                                  )
                                ),
                                Text(
                                  model.weather?.cond,
                                  style: new TextStyle(
                                    color: Colors.white,
                                    fontSize: 45.0
                                  )
                                ),
                                Text(
                                  model.weather?.hum,
                                  style: new TextStyle(
                                    color: Colors.white,
                                    fontSize: 30.0
                                  ),
                                )
                              ],
                            );
                    },
                  ),
                )
              )
            ],
          )
        ],
      ),
    );
  }

}
複製程式碼

在使用ScopedModelScopedModelDescendant都使用到了泛型

相關文章