Fluent news client – 07 provider, authentication and authorization, skeleton screen, disk cache

Time:2021-10-14

Fluent news client - 07 provider, authentication and authorization, skeleton screen, disk cache

Station B video

https://www.bilibili.com/vide…
https://www.bilibili.com/vide…
https://www.bilibili.com/vide…
https://www.bilibili.com/vide…
https://www.bilibili.com/vide…
https://www.bilibili.com/vide…

Objectives of this section

  • The first login displays the welcome interface
  • Offline login
  • Provider response data management
  • Realize app color gray processing
  • Logout login
  • HTTP status 401 authentication authorization
  • Home disk cache
  • Home page caching strategy, delay 1 ~ 3 seconds
  • Home skeleton screen

video

resources

Welcome interface and offline login are displayed for the first time

Fluent news client - 07 provider, authentication and authorization, skeleton screen, disk cache

  • lib/global.dart
///Open for the first time
  static bool isFirstOpen = false;

  ///Log in offline
  static bool isOfflineLogin = false;

  /// init
  static Future init() async {
    ...

    //The reading device is turned on for the first time
    isFirstOpen = !StorageUtil().getBool(STORAGE_DEVICE_ALREADY_OPEN_KEY);
    if (isFirstOpen) {
      StorageUtil().setBool(STORAGE_DEVICE_ALREADY_OPEN_KEY, true);
    }

    //Read offline user information
    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(),
    );
  }
}

The provider implements dynamic gray processing

https://pub.flutter-io.cn/pac…

Step 1: install dependency

dependencies:
  provider: ^4.0.4

Step 2: create a response data class

  • lib/common/provider/app.dart
import 'package:flutter/material.dart';

///System corresponding status
class AppState with ChangeNotifier {
  bool _isGrayFilter;

  get isGrayFilter => _isGrayFilter;

  AppState({bool isGrayFilter = false}) {
    this._isGrayFilter = isGrayFilter;
  }
}

Step 3: initial response data

Method 1: first create a data object and then mount it

  • lib/global.dart
///Application status
  static AppState appState = AppState();
  • lib/main.dart
void main() => Global.init().then((e) => runApp(
      MultiProvider(
        providers: [
          ChangeNotifierProvider<AppState>.value(
            value: Global.appState,
          ),
        ],
        child: MyApp(),
      ),
    ));

Method 2: create an object when mounting

  • lib/main.dart
void main() => Global.init().then((e) => runApp(
      MultiProvider(
        providers: [
          ChangeNotifierProvider<AppState>(
            Create: (_) => new AppState(),
          ),
        ],
        child: MyApp(),
      ),
    ));

Step 4: notify data sounding change

  • lib/common/provider/app.dart
class AppState with ChangeNotifier {
  ...

  //Switch gray filter
  switchGrayFilter() {
    _isGrayFilter = !_isGrayFilter;
    notifyListeners();
  }
}

Step 5: sound change after receiving data

Method 1: 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();
          }
        }),
      ),
    ));

Method 2: provider.of

  • lib/pages/account/account.dart
final appState = Provider.of<AppState>(context);

    return Column(
      children: <Widget>[
        MaterialButton(
          onPressed: () {
            appState.switchGrayFilter();
          },
          Child: text ('gray switching ${appstate. Isgrayfilter} '),
        ),
      ],
    );

Multiple response data processing

  • Multiprovider for mounting
  • Consumer2 ~ consumer6 for receiving

Logout login

  • lib/common/utils/authentication.dart
///Check whether there is a token
Future<bool> isAuthenticated() async {
  var profileJSON = StorageUtil().getJSON(STORAGE_USER_PROFILE_KEY);
  return profileJSON != null ? true : false;
}

///Delete cache token
Future deleteAuthentication() async {
  await StorageUtil().remove(STORAGE_USER_PROFILE_KEY);
  Global.profile = null;
}

///Login again
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 ('user: ${global. Profile. DisplayName} '),
        Divider(),
        MaterialButton(
          onPressed: () {
            goLoginPage(context);
          },
          Child: text ('exit '),
        ),
      ],
    );
  }
}

HTTP status 401 authentication authorization

Dio encapsulates the context object buildcontext context of the interface

  • 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,
    });
    ...
  }

Error handling 401 go to the login interface

  • lib/common/utils/http.dart
//Add interceptor
    dio.interceptors
        .add(InterceptorsWrapper(onRequest: (RequestOptions options) {
      return options; //continue
    }, onResponse: (Response response) {
      return response; // continue
    }, onError: (DioError e) {
      ErrorEntity eInfo = createErrorEntity(e);
      //Error prompt
      toastInfo(msg: eInfo.message);
      //Error interactive processing
      var context = e.request.extra["context"];
      if (context != null) {
        switch (eInfo.code) {
          Case 401: // you don't have permission to log in again
            goLoginPage(context);
            break;
          default:
        }
      }
      return eInfo;
    }));

Home disk cache

  • lib/common/utils/net_cache.dart
//Strategy 1 memory cache takes precedence and 2 disk cache takes precedence

      //1 memory cache
      var ob = cache[key];
      if (ob != null) {
        //If the cache has not expired, the cache content is returned
        if ((DateTime.now().millisecondsSinceEpoch - ob.timeStamp) / 1000 <
            CACHE_MAXAGE) {
          return cache[key].response;
        } else {
          //If it has expired, delete the cache and continue to request from the server
          cache.remove(key);
        }
      }

      //2 disk cache
      if (cacheDisk) {
        var cacheData = StorageUtil().getJSON(key);
        if (cacheData != null) {
          return Response(
            statusCode: 200,
            data: cacheData,
          );
        }
      }

Home page caching strategy, delay 1 ~ 3 seconds

  • lib/pages/main/channels_widget.dart
//If there is a disk cache, delay 3 seconds to pull the update file
  _loadLatestWithDiskCache() {
    if (CACHE_ENABLE == true) {
      var cacheData = StorageUtil().getJSON(STORAGE_INDEX_NEWS_CACHE_KEY);
      if (cacheData != null) {
        Timer(Duration(seconds: 3), () {
          _controller.callRefresh();
        });
      }
    }
  }

Home skeleton screen

https://pub.flutter-io.cn/pac…

  • lib/pages/main/main.dart
  @override
  Widget build(BuildContext context) {
    return _newsPageList == null
        ? cardListSkeleton()
        : EasyRefresh(
            enableControlFinishRefresh: true,
            controller: _controller,
            ...