B站視訊
www.bilibili.com/video/BV1vV… www.bilibili.com/video/BV1SA… www.bilibili.com/video/BV1jt… www.bilibili.com/video/BV1wt… www.bilibili.com/video/BV1b5… www.bilibili.com/video/BV11z…
本節目標
- 第一次登入顯示歡迎介面
- 離線登入
- Provider 響應資料管理
- 實現 APP 色彩灰度處理
- 登出登入
- Http Status 401 認證授權
- 首頁磁碟快取
- 首頁快取策略,延遲 1~3 秒
- 首頁骨架屏
視訊
資源
-
藍湖設計稿(加微信給授權 ducafecat) lanhuapp.com/url/wbhGq
-
YAPI 介面管理 yapi.demo.qunar.com/
-
參考
第一次顯示歡迎介面、離線登入
- lib/global.dart
/// 是否第一次開啟
static bool isFirstOpen = false;
/// 是否離線登入
static bool isOfflineLogin = false;
/// init
static Future init() async {
...
// 讀取裝置第一次開啟
isFirstOpen = !StorageUtil().getBool(STORAGE_DEVICE_ALREADY_OPEN_KEY);
if (isFirstOpen) {
StorageUtil().setBool(STORAGE_DEVICE_ALREADY_OPEN_KEY, true);
}
// 讀取離線使用者資訊
var _profileJSON = StorageUtil().getJSON(STORAGE_USER_PROFILE_KEY);
if (_profileJSON != null) {
profile = UserLoginResponseEntity.fromJson(_profileJSON);
isOfflineLogin = true;
}
複製程式碼
- lib/pages/index/index.dart
class IndexPage extends StatefulWidget {
IndexPage({Key key}) : super(key: key);
@override
_IndexPageState createState() => _IndexPageState();
}
class _IndexPageState extends State<IndexPage> {
@override
Widget build(BuildContext context) {
ScreenUtil.init(
context,
width: 375,
height: 812 - 44 - 34,
allowFontScaling: true,
);
return Scaffold(
body: Global.isFirstOpen == true
? WelcomePage()
: Global.isOfflineLogin == true ? ApplicationPage() : SignInPage(),
);
}
}
複製程式碼
Provider 實現動態灰度處理
pub.flutter-io.cn/packages/pr…
步驟 1:安裝依賴
dependencies:
provider: ^4.0.4
複製程式碼
步驟 2:建立響應資料類
- lib/common/provider/app.dart
import 'package:flutter/material.dart';
/// 系統相應狀態
class AppState with ChangeNotifier {
bool _isGrayFilter;
get isGrayFilter => _isGrayFilter;
AppState({bool isGrayFilter = false}) {
this._isGrayFilter = isGrayFilter;
}
}
複製程式碼
步驟 3:初始響應資料
方式一:先建立資料物件,再掛載
- lib/global.dart
/// 應用狀態
static AppState appState = AppState();
複製程式碼
- lib/main.dart
void main() => Global.init().then((e) => runApp(
MultiProvider(
providers: [
ChangeNotifierProvider<AppState>.value(
value: Global.appState,
),
],
child: MyApp(),
),
));
複製程式碼
方式二:掛載時,建立物件
- lib/main.dart
void main() => Global.init().then((e) => runApp(
MultiProvider(
providers: [
ChangeNotifierProvider<AppState>(
Create: (_) => new AppState(),
),
],
child: MyApp(),
),
));
複製程式碼
步驟 4:通知資料發聲變化
- lib/common/provider/app.dart
class AppState with ChangeNotifier {
...
// 切換灰色濾鏡
switchGrayFilter() {
_isGrayFilter = !_isGrayFilter;
notifyListeners();
}
}
複製程式碼
步驟 5:收到資料發聲變化
方式一:Consumer
- lib/main.dart
void main() => Global.init().then((e) => runApp(
MultiProvider(
providers: [
ChangeNotifierProvider<AppState>.value(
value: Global.appState,
),
],
child: Consumer<AppState>(builder: (context, appState, _) {
if (appState.isGrayFilter) {
return ColorFiltered(
colorFilter: ColorFilter.mode(Colors.white, BlendMode.color),
child: MyApp(),
);
} else {
return MyApp();
}
}),
),
));
複製程式碼
方式二:Provider.of
- lib/pages/account/account.dart
final appState = Provider.of<AppState>(context);
return Column(
children: <Widget>[
MaterialButton(
onPressed: () {
appState.switchGrayFilter();
},
child: Text('灰色切換 ${appState.isGrayFilter}'),
),
],
);
複製程式碼
多個響應資料處理
-
掛載用 MultiProvider
-
接收用 Consumer2 ~ Consumer6
登出登入
- lib/common/utils/authentication.dart
/// 檢查是否有 token
Future<bool> isAuthenticated() async {
var profileJSON = StorageUtil().getJSON(STORAGE_USER_PROFILE_KEY);
return profileJSON != null ? true : false;
}
/// 刪除快取 token
Future deleteAuthentication() async {
await StorageUtil().remove(STORAGE_USER_PROFILE_KEY);
Global.profile = null;
}
/// 重新登入
Future goLoginPage(BuildContext context) async {
await deleteAuthentication();
Navigator.pushNamedAndRemoveUntil(
context, "/sign-in", (Route<dynamic> route) => false);
}
複製程式碼
- lib/pages/account/account.dart
class _AccountPageState extends State<AccountPage> {
@override
Widget build(BuildContext context) {
final appState = Provider.of<AppState>(context);
return Column(
children: <Widget>[
Text('使用者: ${Global.profile.displayName}'),
Divider(),
MaterialButton(
onPressed: () {
goLoginPage(context);
},
child: Text('退出'),
),
],
);
}
}
複製程式碼
Http Status 401 認證授權
dio 封裝介面的上下文物件 BuildContext context
- lib/common/utils/http.dart
Future post(
String path, {
@required BuildContext context,
dynamic params,
Options options,
}) async {
Options requestOptions = options ?? Options();
requestOptions = requestOptions.merge(extra: {
"context": context,
});
...
}
複製程式碼
錯誤處理 401 去登入介面
- lib/common/utils/http.dart
// 新增攔截器
dio.interceptors
.add(InterceptorsWrapper(onRequest: (RequestOptions options) {
return options; //continue
}, onResponse: (Response response) {
return response; // continue
}, onError: (DioError e) {
ErrorEntity eInfo = createErrorEntity(e);
// 錯誤提示
toastInfo(msg: eInfo.message);
// 錯誤互動處理
var context = e.request.extra["context"];
if (context != null) {
switch (eInfo.code) {
case 401: // 沒有許可權 重新登入
goLoginPage(context);
break;
default:
}
}
return eInfo;
}));
複製程式碼
首頁磁碟快取
- lib/common/utils/net_cache.dart
// 策略 1 記憶體快取優先,2 然後才是磁碟快取
// 1 記憶體快取
var ob = cache[key];
if (ob != null) {
//若快取未過期,則返回快取內容
if ((DateTime.now().millisecondsSinceEpoch - ob.timeStamp) / 1000 <
CACHE_MAXAGE) {
return cache[key].response;
} else {
//若已過期則刪除快取,繼續向伺服器請求
cache.remove(key);
}
}
// 2 磁碟快取
if (cacheDisk) {
var cacheData = StorageUtil().getJSON(key);
if (cacheData != null) {
return Response(
statusCode: 200,
data: cacheData,
);
}
}
複製程式碼
首頁快取策略,延遲 1~3 秒
- lib/pages/main/channels_widget.dart
// 如果有磁碟快取,延遲3秒拉取更新檔案
_loadLatestWithDiskCache() {
if (CACHE_ENABLE == true) {
var cacheData = StorageUtil().getJSON(STORAGE_INDEX_NEWS_CACHE_KEY);
if (cacheData != null) {
Timer(Duration(seconds: 3), () {
_controller.callRefresh();
});
}
}
}
複製程式碼
首頁骨架屏
pub.flutter-io.cn/packages/pk…
- lib/pages/main/main.dart
@override
Widget build(BuildContext context) {
return _newsPageList == null
? cardListSkeleton()
: EasyRefresh(
enableControlFinishRefresh: true,
controller: _controller,
...
複製程式碼