Fluent news client – 05 appdata, cache, fiddle, iconfont, main interface construction

Time:2021-10-16

Fluent news client - 05 appdata, cache, fiddle, iconfont, main interface construction

Station B video

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

Objectives of this section

  • Global data, response data, persistence
  • HTTP get cache
  • HTTP proxy proxy
  • Fiddle packet capture tool
  • Iconfont font library
  • Main interface construction
  • Bottomnavigationbar navigation control
  • Write API interface code

Client data management

data type

  • Global data

Stored in memory

User data, language pack

  • Response data

Stored in memory

User login status, multilingual, skin style

Redux、Bloc、provider

  • Persistence

App remains on disk

Browser cookie localstorage

Write global management

  • lib/global.dart
///Global configuration
class Global {
  ///User configuration
  static UserLoginResponseEntity profile = UserLoginResponseEntity(
    accessToken: null,
  );

  ///Release
  static bool get isRelease => bool.fromEnvironment("dart.vm.product");

  /// init
  static Future init() async {
    //Initial operation
    WidgetsFlutterBinding.ensureInitialized();

    //Tool initial
    await StorageUtil.init();
    HttpUtil();

    //Read offline user information
    var _profileJSON = StorageUtil().getJSON(STORAGE_USER_PROFILE_KEY);
    if (_profileJSON != null) {
      profile = UserLoginResponseEntity.fromJson(_profileJSON);
    }

    //HTTP cache

    //Android status bar is transparent
    if (Platform.isAndroid) {
      SystemUiOverlayStyle systemUiOverlayStyle =
          SystemUiOverlayStyle(statusBarColor: Colors.transparent);
      SystemChrome.setSystemUIOverlayStyle(systemUiOverlayStyle);
    }
  }

  //Persistent user information
  static Future<bool> saveProfile(UserLoginResponseEntity userResponse) {
    profile = userResponse;
    return StorageUtil()
        .setJSON(STORAGE_USER_PROFILE_KEY, userResponse.toJson());
  }
}

Call run

  • lib/main.dart
void main() => Global.init().then((e) => runApp(MyApp()));

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container();
  }
}

HTTP memory cache

Cache policy

Fluent news client - 05 appdata, cache, fiddle, iconfont, main interface construction

code

  • Cache tool class lib / common / utils / net_ cache.dart
import 'dart:collection';

import 'package:dio/dio.dart';
import 'package:flutter_ducafecat_news/common/values/values.dart';

class CacheObject {
  CacheObject(this.response)
      : timeStamp = DateTime.now().millisecondsSinceEpoch;
  Response response;
  int timeStamp;

  @override
  bool operator ==(other) {
    return response.hashCode == other.hashCode;
  }

  @override
  int get hashCode => response.realUri.hashCode;
}

class NetCache extends Interceptor {
  //To ensure that the iterator order is consistent with the object insertion time, we use LinkedHashMap
  var cache = LinkedHashMap<String, CacheObject>();

  @override
  onRequest(RequestOptions options) async {
    if (!CACHE_ENABLE) return options;

    //Whether the refresh flag is "drop-down refresh"
    bool refresh = options.extra["refresh"] == true;

    //If it is a drop-down refresh, delete the relevant cache first
    if (refresh) {
      if (options.extra["list"] == true) {
        //If it is a list, all caches containing the current path in the URL will be deleted (simple implementation, not accurate)
        cache.removeWhere((key, v) => key.contains(options.path));
      } else {
        //If it is not a list, only caches with the same URI are deleted
        delete(options.uri.toString());
      }
      return options;
    }

    //Get request, enable cache
    if (options.extra["noCache"] != true &&
        options.method.toLowerCase() == 'get') {
      String key = options.extra["cacheKey"] ?? options.uri.toString();
      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);
        }
      }
    }
  }

  @override
  onError(DioError err) async {
    //Error status not cached
  }

  @override
  onResponse(Response response) async {
    //If caching is enabled, the returned results are saved to the cache
    if (CACHE_ENABLE) {
      _saveCache(response);
    }
  }

  _saveCache(Response object) {
    RequestOptions options = object.request;

    //Cache only get requests
    if (options.extra["noCache"] != true &&
        options.method.toLowerCase() == "get") {
      //If the number of caches exceeds the maximum number limit, the oldest record is removed first
      if (cache.length == CACHE_MAXCOUNT) {
        cache.remove(cache[cache.keys.first]);
      }
      String key = options.extra["cacheKey"] ?? options.uri.toString();
      cache[key] = CacheObject(object);
    }
  }

  void delete(String key) {
    cache.remove(key);
  }
}
  • Dio encapsulation lib / common / utils / http.dart
//Add memory cache
  HttpUtil._internal() {
    ...
    dio.interceptors.add(NetCache());
    ...
  }

  //Modify get request
  ///Restful get operation
  ///Refresh drop-down refresh default false
  ///Whether nocache does not cache is true by default
  ///Is the list false by default
  ///Cachekey
  Future get(
    String path, {
    dynamic params,
    Options options,
    bool refresh = false,
    bool noCache = !CACHE_ENABLE,
    bool list = false,
    String cacheKey,
  }) async {
    try {
      Options requestOptions = options ?? Options();
      requestOptions = requestOptions.merge(extra: {
        "refresh": refresh,
        "noCache": noCache,
        "list": list,
        "cacheKey": cacheKey,
      });
      Map<String, dynamic> _authorization = getAuthorizationHeader();
      if (_authorization != null) {
        requestOptions = requestOptions.merge(headers: _authorization);
      }

      var response = await dio.get(path,
          queryParameters: params,
          options: requestOptions,
          cancelToken: cancelToken);
      return response.data;
    } on DioError catch (e) {
      throw createErrorEntity(e);
    }
  }

HTTP proxy + fiddle packet capture

Install fiddle

https://www.telerik.com/downl…

Fluent news client - 05 appdata, cache, fiddle, iconfont, main interface construction

Dio join proxy

  • lib/common/utils/http.dart
if (!Global.isRelease && PROXY_ENABLE) {
    (dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate =
        (client) {
      client.findProxy = (uri) {
        return "PROXY $PROXY_IP:$PROXY_PORT";
      };
      //The agent tool will provide a self signed certificate for capturing packets, which will not pass the certificate verification, so we disable the certificate verification
      client.badCertificateCallback =
          (X509Certificate cert, String host, int port) => true;
    };
  }

Icomont font library

Introduction process

  • Sign in

https://www.iconfont.cn

  • Create font item

Fluent news client - 05 appdata, cache, fiddle, iconfont, main interface construction

  • Font file in

assets/fonts/iconfont.ttf

  • pubspec.yaml
  fonts:
    ...
    - family: Iconfont
      fonts:
        - asset: assets/fonts/iconfont.ttf
  • lib/common/utils/iconfont.dart
import 'package:flutter/material.dart';

class Iconfont {
    // iconName: share
  static const share = IconData(
    0xe60d,
    fontFamily: 'Iconfont',
    matchTextDirection: true,
  );

  ...
}

Automatically generate font library code

https://github.com/ymzuiku/ic…

  • Pull project and compile
#Pull item
> git clone https://github.com/ymzuiku/iconfont_builder

#Update package
> pub get

#Installation tools
> pub global activate iconfont_builder

#Check environment configuration
export PATH=${PATH}:~/.pub-cache/bin
  • Refer to my configuration
# flutter sdk
export PATH=${PATH}:~/Documents/sdk/flutter/bin

# dart sdk
export PATH=${PATH}:~/Documents/sdk/flutter/bin/cache/dart-sdk/bin
export PATH=${PATH}:~/.pub-cache/bin

#Fluent IO domestic image
export PUB_HOSTED_URL=https://pub.flutter-io.cn
export FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn

# android
export ANDROID_HOME=~/Library/Android/sdk
export PATH=${PATH}:${ANDROID_HOME}/platform-tools
export PATH=${PATH}:${ANDROID_HOME}/tools
  • Generate font class
CD your project root directory
iconfont_builder --from ./assets/fonts --to ./lib/common/utils/iconfont.dart

Write API business code

  • Yapi configuration

Import Doc / api.json

Fluent news client - 05 appdata, cache, fiddle, iconfont, main interface construction

  • code

Fluent news client - 05 appdata, cache, fiddle, iconfont, main interface construction

Build the main interface framework

Fluent news client - 05 appdata, cache, fiddle, iconfont, main interface construction

  • Frame page lib / pages / application / application.dart
...
class _ApplicationPageState extends State<ApplicationPage>
    with SingleTickerProviderStateMixin {
  //Current tab page number
  int _page = 0;
  //Tab page title
  final List<String> _tabTitles = [
    'Welcome',
    'Cagegory',
    'Bookmarks',
    'Account'
  ];
  //Page controller
  PageController _pageController;

  //Bottom navigation item
  final List<BottomNavigationBarItem> _bottomTabs = <BottomNavigationBarItem>[...];

  //Tab bar animation
  void _handleNavBarTap(int index) {
    ...
  }

  //Tab column page switching
  void _handlePageChanged(int page) {
    ...
  }

  //Top navigation
  Widget _buildAppBar() {
    return Container();
  }

  //Content page
  Widget _buildPageView() {
    return Container();
  }

  //Bottom navigation
  Widget _buildBottomNavigationBar() {
    return Container();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: _buildAppBar(),
      body: _buildPageView(),
      bottomNavigationBar: _buildBottomNavigationBar(),
    );
  }
}

Write home page code

Fluent news client - 05 appdata, cache, fiddle, iconfont, main interface construction

  • Homepage Code: lib / pages / main / main.dart
...

class _MainPageState extends State<MainPage> {
  NewsPageListResponseEntity _ newsPageList; //  News page turning
  NewsRecommendResponseEntity _ newsRecommend; //  News recommendation
  List<CategoryResponseEntity> _ categories; //  classification
  List<ChannelResponseEntity> _ channels; //  channel

  String _ selCategoryCode; //  Selected classification code

  @override
  void initState() {
    super.initState();
    _loadAllData();
  }

  //Read all data
  _loadAllData() async {
    ...
  }

  //Category menu
  Widget _buildCategories() {
    return Container();
  }
  //Implement business before extraction

  //Recommended reading
  Widget _buildRecommend() {
    return Container();
  }

  //Channel
  Widget _buildChannels() {
    return Container();
  }

  //News list
  Widget _buildNewsList() {
    return Container();
  }

  //Ad banner
  //Mail subscription
  Widget _buildEmailSubscribe() {
    return Container();
  }

  @override
  Widget build(BuildContext context) {
    return SingleChildScrollView(
      child: Column(
        children: <Widget>[
          _buildCategories(),
          _buildRecommend(),
          _buildChannels(),
          _buildNewsList(),
          _buildEmailSubscribe(),
        ],
      ),
    );
  }
}
  • Extract news categories lib / pages / main / categories_ widget.dart
Widget newsCategoriesWidget(
    {List<CategoryResponseEntity> categories,
    String selCategoryCode,
    Function(CategoryResponseEntity) onTap}) {
  return categories == null
      ? Container()
      : SingleChildScrollView(
          scrollDirection: Axis.horizontal,
          child: Row(
            children: categories.map<Widget>((item) {
              return Container(
                alignment: Alignment.center,
                height: duSetHeight(52),
                padding: EdgeInsets.symmetric(horizontal: 8),
                child: GestureDetector(
                  child: Text(
                    item.title,
                    style: TextStyle(
                      color: selCategoryCode == item.code
                          ? AppColors.secondaryElementText
                          : AppColors.primaryText,
                      fontSize: duSetFontSize(18),
                      fontFamily: 'Montserrat',
                      fontWeight: FontWeight.w600,
                    ),
                  ),
                  onTap: () => onTap(item),
                ),
              );
            }).toList(),
          ),
        );
}

Blue lake design draft

https://lanhuapp.com/url/lYuz1
Password: gskl

Blue lake charges now, so please upload XD design draft by yourself to view the tag
It’s difficult to share the commercial design documents directly. You can contact ducafecat through wechat

Yapi interface management

http://yapi.demo.qunar.com/

Git code

https://github.com/ducafecat/…

tool

Vscode plug-in

video