0x00 前言
前面四篇文章:
- Flutter實戰1 --- 寫一個天氣查詢的APP
- Flutter實戰2 --- 寫一個天氣查詢的APP
- FFlutter實戰3 --- PC上執行Flutter APP
- Flutter實戰4 -- 天氣查詢APP重構之狀態管理(InheritedWidget)
在第4篇文章中,為了方便管理狀態,我們介紹了InheritedWidget,今天介紹ScopedModel,這是一個封裝的InheritedWidget的庫,使用起來更方便。
0x01 程式碼
本篇文章所涉及的程式碼:
分支: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使用,主要是下面三個類:
-
Model
你要繼承
Model
這個類,實現自己的Models,這個Module類裡,持有相關的資料,及實現一些業務邏輯,可以監聽Model裡的資料變化 -
ScopedModel
ScopedModel是一個Widget,確定Model的使用範圍:像使用InheritedWidget一樣,你需要用ScopedModel包其他Widget,而且Model也是包在ScopedModel裡的,這樣Model就可以沿著Widget樹向下傳遞。
-
ScopedModelDescendant ScopedModelDescendant也是一個Widget,在子Widget獲取Model使用:使用ScopedModelDescendant,是為了在子Widget裡找到相應的Model。只要是Model發生更改,它就會自動重建。
0x05 重構 -- 實現Model
新增了兩個Model,分別是:
- 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();
}
}
複製程式碼
- 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....
}
)
複製程式碼
- 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))
);
},
),
);
}
);
},
)
);
}
}
複製程式碼
- 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
),
)
],
);
},
),
)
)
],
)
],
),
);
}
}
複製程式碼
在使用ScopedModel
和 ScopedModelDescendant
都使用到了泛型