Flutter实战5 -- 天气查询APP重构之状态管理(ScopedModel)

2,609 阅读3分钟

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都使用到了泛型