introduce
Colorful youth, confused youth, ignorant youth, tears of youth, responsibility of youth, youth graceful, the beauty of youth all spread in the scenery along the way, confused, sour, laughter in the memory of the sky carrying the dream and flying, youth has become an eternal heart
This article takes you through a step-by-step build up of the Flutter project architecture for direct integration with your project. The project mainly uses the following technology stack, xiaobian holds the purpose of sharing, for you to explain
1. Globally catch exceptions
Routing (Route) 2.
3. Dio (network)
4.OverlayEntry
5. Configuration of Network Dio Packet capture tool (ALice)
6. State Management (Provider)
7. Notification (this is written by myself, very convenient, similar to EventBus)
Global catch exception
In Flutter, some anomalies can be captured, while others cannot. So, we want to make the error log report to the server, convenient online tracking problems, how to do? Here’s one thing, “runZoned” if you can’t capture it. The code is as follows, and there are detailed comments in the code, which will not be explained here.
Void main() {runZoned FlutterError. OnError = {a flutter can catch exceptions that a try catch cannot catch (FlutterErrorDetails errorDetails) {if (application.debug) {/// Test environment log directly printed on the console FlutterError.dumpErrorToConsole(errorDetails); } else {/// Redirects to runZone on production environment handle zone.current. HandleUncaughtError (errordetails.exception, errorDetails.stack); } reportErrorAndLog(errorDetails); }; WidgetsFlutterBinding.ensureInitialized(); GlobalKey<NavigatorState> globalKey = new GlobalKey<NavigatorState>(); Application.globalKey = globalKey; /// Dio Network packet capture tool Alice Alice = Alice(showInspectorOnShake: true, showNotification: true, navigatorKey: globalKey); Application.alice = alice; // initialize the network configuration httpManager.initnet (); RunZoned (() => runApp(MultiProvider(providers: ChangeNotifierProvider(create: (_) => CounterProvider()), ], child: MyApp(), )), zoneSpecification: ZoneSpecification( print: (Zone self, ZoneDelegate parent, Zone Zone, String line) {/// print all logs},), onError: (Object obj, StackTrace stack) { var detail = makeDetails(obj, stack); reportErrorAndLog(detail); }); } void reporAndLog (FlutterErrorDetails errorDetails) {/// Error log reporting server} /// Build error message FlutterErrorDetails makeDetails(Object obj, StackTrace stack) { FlutterErrorDetails details = FlutterErrorDetails(stack: stack, exception: obj); return details; }Copy the code
Routing related
Route Hop Configuration
There are two ways to jump. One is to use the Widget directly, the other is to use routeName. Here xiaobian for you to explain the routeName jump
The route jump wrapper class is attached first
import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; /// Created by zhengxiangke /// des: Class NavigatorUtil {// static void push(BuildContext, Widget Widget) {navigator.push (context, MaterialPageRoute(builder: (context) => widget)); } static void pushName(BuildContext context, String name, {Object arguments}) { Navigator.pushNamed(context, name, arguments: arguments); } static void pop(BuildContext context) {navigator.of (context).pop(context); } // all pages on the specified routing page are destroyed. Static void popUntil(BuildContext Context, String routeName) {Navigator. PopUntil (context, routeName) ModalRoute.withName(routeName)); } // replace the current page position in the stack with the jump page, when the new page entered, Dispose method static void pushReplacementNamed(BuildContext Context, String routeName, {Object arguments}) { Navigator.of(context).pushReplacementNamed(routeName, arguments: arguments); }}Copy the code
We can see the routeName in the code, the routeName this can be configured ourselves, in short, according to the path to the specified page. The route configuration is as follows
class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( localeResolutionCallback: (Locale locale, Iterable<Locale> supportedLocales) { //print("change language"); return locale; }, navigatorKey: application. globalKey, // routes: Route onGenerateRoute: onGenerateRoute, navigatorObservers: GLObserver(), home: MyHomePage(),); }}Copy the code
final routes = { '/second' : (context) => SecondPage(), '/NestedScrollViewDemo' : (context, {arguments}) => NestedScrollViewDemo(value: arguments['value'] as String), '/ProviderDemo': (context) => ProviderDemo() }; // ignore: missing_return, top_level_function_literal_block final onGenerateRoute = (settings) { Function pageContentBuilder = routes[settings.name]; Route route; if (pageContentBuilder ! = null) { if (settings.arguments ! = null) {route = MaterialPageRoute(Settings: Settings, Builder: (context) => pageContentBuilder(context, arguments: settings.arguments)); return route; } else {route = MaterialPageRoute(Settings: Settings, Builder: (context) => pageContentBuilder(context)); return route; }}};Copy the code
The observer
Add observer to get user behavior data (GLObserver)
@override Widget build(BuildContext context) { return MaterialApp( localeResolutionCallback: (Locale locale, Iterable<Locale> supportedLocales) { //print("change language"); return locale; }, navigatorKey: application. globalKey, // routes: Route onGenerateRoute: onGenerateRoute, navigatorObservers: GLObserver(), home: MyHomePage(),); }Copy the code
Dio related
Dio is a powerful Dart Http request library with support for Restful apis, FormData, interceptors, request cancellation, Cookie management, file upload/download, timeouts, custom adapters, and more…
Dependencies: dio: ^3.0.9 // Use the latest version of branch 3.0.0 on pubCopy the code
The basic configuration
Xiaobian take you to write a Dio singleton, in this singleton configuration Dio basic configuration
Const String BASEURL = "; /// This is a domain name class HttpConfig { static const baseUrl = BASEURL; static const timeout = 5000; static const codeSuccess = 10000; } class HttpManager { factory HttpManager() => getInstance(); static HttpManager get install => getInstance(); static HttpManager _install; static Dio dio; Static HttpManager getInstance() {if (_install == null) {_install = HttpManager._internal(); } return _install; } static void initNet() {dio = dio (BaseOptions(baseUrl: httpconfig. baseUrl, contentType: 'application/x-www-form-urlencoded', connectTimeout: HttpConfig.timeout, receiveTimeout: HttpConfig.timeout )); }}Copy the code
Set the agent
So one day, with this requirement in the background, the test came up and said, how do YOU capture information on the web? Dio provided an agent for us, and the test could view network information based on packet capture tools such as Chanles
If (application.proxy) {/// For proxy packet capture (dio.httpClientAdapter as DefaultHttpClientAdapter). OnHttpClientCreate = (client) { Client. FindProxy = (uri) {//// PROXY localhost:8888 return 'PROXY localhost:8888'; }; }; }Copy the code
The interceptor
We can add some common parameters in the interceptor, such as user information, mobile phone information, App version information, etc. We can also print the REQUEST URL, request header, request body information. You can also sign parameters. I’m not going to go into the signature here,
Dio.interceptors. add(CustomInterceptors());Copy the code
/// des: Class CustomInterceptors extends InterceptorsWrapper {@Override Future onRequest(RequestOptions) The options) {/ / / set the public request header in intercept options. The headers = {HttpHeaders. AuthorizationHeader: 'this is a token'}; If (application.debug) {try {print(" request URL: ${options.path}"); Print (' request header: '+ options.headers. ToString ()); Print (' request body: '+ options.data); } catch (e) { print(e); } } return super.onRequest(options); } @override Future onResponse(Response response) async{ LoadingUtil.closeLoading(); if (Application.debug) { print('code=${response.statusCode.toString()} ==data=${response.data .toString()}'); } return super.onResponse(response); } @override Future onError(DioError err) { // TODO: implement onError LoadingUtil.closeLoading(); if (err.type == DioErrorType.CONNECT_TIMEOUT || err.type == DioErrorType.RECEIVE_TIMEOUT || err.type == Dioerrortype. SEND_TIMEOUT) {fluttertoast. showToast(MSG: 'request timed out '); } else {fluttertoast. showToast(MSG: 'service exception '); } return super.onError(err); }}Copy the code
ALice
This is a network request view library, with this does not need to specify proxy, very convenient. Let’s do Alice interception for DIO to see the requests from DIO
Dependencies: Alice: 0.1.4 dio. Interceptors. Add (Application. Alice. GetDioInterceptor ());Copy the code
Note: ALice must configure navigatorKey
GlobalKey<NavigatorState> globalKey = new GlobalKey<NavigatorState>(); Application.globalKey = globalKey; /// Dio Network packet capture tool Alice Alice = Alice(showInspectorOnShake: true, showNotification: true, navigatorKey: globalKey); Application.alice = alice;Copy the code
class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( navigatorKey:Application.globalKey, ], home: MyHomePage(), ); }}Copy the code
Provider
Flutter state management is actually the binding and refreshing of data and views. This corresponds to H5, which is a little bit easier to understand, and this concept also comes from the front; Corresponding to the client, is listening for callbacks, like EventBus.
In short, a change in the attribute of a variable in a listening class refreshes the Widget page that uses that variable
Dependencies: the provider: ^ 4.3.2 + 2Copy the code
Talk about the central idea of this library
1. Register
2. Define the class
3. The assignment
Value of 4.
registered
ChangeNotifierProvider(providers: [/// // providers: /// ///) (_) => CounterProvider()), ], child: MyApp(), ))Copy the code
Define the class
class CounterProvider with ChangeNotifier { int count = 0; void addCount() { count ++; notifyListeners(); }}Copy the code
The assignment
Provider.of<CounterProvider>(context, listen: false).addCount()
Copy the code
The values
context.watch<CounterProvider>().count
Copy the code
notice
I used this notification when I was doing Android development. Later did Flutter development a year ago and drew on its ideas to create this notice
Here’s how notification works:
We know that EventBus has an action event and a data object that can be passed. Register a notification in the page initialization life cycle and destroy it in the page destruction life cycle. Call send notifications where you need to send notifications to refresh the data. Each Action corresponds to which notification to send. The notification data is a generic Object that can send strings, objects, arrays, and other data
Notification Management
Several methods are provided in this class
1. Registration notification
2. Destruction notice
3. Send notifications
/// This is an example /// the type in initState() registration <> should correspond to the data type sent. It can be String int bool and model list IUpdateViewManager.instance.registerIUPdateView(UpdateView(NoticeAction.action1, /// IUpdateView<List<String>>( /// callback: (List<String> msg) { /// print(msg); / / / / / /}))); , / / / send a notification Here you can send any type of data Because the paradigm / / / IUpdateViewManager. Instance. NotifyIUpdateView (NoticeAction. Action1, [' xx ', 'VVV']); /// Dispose dispose note: Registration has to be cancelled This is the corresponding Otherwise there will be a function not normal treats. / / / IUpdateViewManager instance. UnRegistIUpdateView (NoticeAction. Action1); class IUpdateViewManager{ List<UpdateView> updateViews = []; // Factory mode factory IUpdateViewManager() =>_getInstance(); static IUpdateViewManager get instance => _getInstance(); static IUpdateViewManager _instance; Iupdateviewmanager._internal () {// initialize} static IUpdateViewManager _getInstance() {_instance?? = IUpdateViewManager._internal(); return _instance; Void registerIUPdateView(UpdateView) {void registerIUPdateView(UpdateView) { updateView); Void notifyIUpdateView <T>(String action, T T) {if (updateViews! = null && updateViews.isNotEmpty) { for (var item in updateViews) { if (item.action == action) { item.iUpdateView.updateView(t); break; }}}} /// Dispose method void unRegistIUpdateView(String action) {if (updateViews! = null && updateViews.isNotEmpty) { updateViews.remove(UpdateView(action, null)); } // This class is the action that uses the notification of this class. This is a constant class NoticeAction {static const String action1 = 'action1'; static const String action2 = 'action2'; }Copy the code
Second, the notification class is as follows
class UpdateView {
String action;
IUpdateView iUpdateView;
UpdateView(this.action, this.iUpdateView);
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is UpdateView &&
runtimeType == other.runtimeType &&
action == other.action;
@override
int get hashCode => action.hashCode;
}
Copy the code
class IUpdateView <T>{ Function(T msg) callback; void updateView (T t) { if (callback ! = null) { callback(t); } } IUpdateView({@required this.callback}); }Copy the code
registration
The type in initState() registration <> has to correspond to the data type that’s being sent either String, int bool, or Model list
IUpdateViewManager.instance.registerIUPdateView(UpdateView(NoticeAction.action1, IUpdateView<List<String>>( callback: (List<String> msg) { print(msg); }))); ,Copy the code
Send a notification
IUpdateViewManager.instance.notifyIUpdateView(NoticeAction.action1, ['xx', 'vvv']);
Copy the code
Destruction of notification
Dispose dispose Dispose Dispose Dispose Dispose Note: There is registration there is cancellation this is the corresponding otherwise there will be abnormal function
IUpdateViewManager.instance.unRegistIUpdateView(NoticeAction.action1);
Copy the code
Architecture article
Xiaobian first talk about the general idea of building the project,
-
We know that every page starts with a loading class, so we use a widget base class that all pages will inherit. This base class provides methods for Appbar, load attempts to show and hide, load failures to retry, and network requests, as well as a buildBody method that must be overridden by all widgets that inherit from the base class, as described in BasePage
-
Networking: This article uses Dio and adds an interceptor in which request information can be printed, an HttpManager to manage a Dio instance of a singleton, and an Alice network viewer to make it easy for testers to view request information. HttpRequest has a request method that defines the request method, the pass method, the failure callback, and the success callback. And return ResultData in the callback (this is a returned data structure encapsulating class)
-
For buried reporting, a GLObserver routing observer has been added to capture simple user behavior
-
Dart For details about error log reporting
-
Because of the complex page interaction, notifications are also indispensable. A page’s behavior will affect the previous page display content or refresh data, so here xiaobi defines two methods: 1.Provider 2
-
CustomerSmartRefresh based on SmartRefresher refresh package
Finally, the code has been uploaded to Github, welcome to download and read if you have any questions add QQ group 883130953
Github.com/zhengxiangk…