background

  • The problem of global popover was encountered during the development of the Flutter – UI
  • Friendly interactive interfaces can produce better user experience. For example, loading effect is performed when the query interface takes a long time or the processing process is time-consuming.
  • The showDialog component of the Flutter component satisfies the popover requirements, but the process can be tricky.
  • The source address

The following points will be used to analyze and implement the popover effect before the interface request

  • ShowDialog introduction
  • Implement a simple popover
  • Access dio package
  • Pop-up critical analysis
  • Implement the global storage context
  • Loading on DIO requests
  • Concurrent requests are processed with loading

Link to this article

  • flutter-ui
  • dio
  • provide

To prepare

  • New project flutter create XXX (if you have a project, use your own project, it will not affect much)
  • Added dio dependencies to pubspec.yaml
Dependencies: flutter: SDK: flutter dio: ^2.1.0# dio dependencies 2019/03/30
Copy the code
  • Create the HTTP folder with the HTTP /index.dart, HTTP /loading.dart files
lib
  |--http   # file
	  |--index.dart  # dio
	  |--loading.dart  #loading
  |--main.dart # entry
Copy the code

ShowDialog introduction

showDialog{
  @required BuildContext context,
  bool barrierDismissible = true,
  @Deprecated(
    'Instead of using the "child" argument, return the child from a closure '
    'provided to the "builder" argument. This will ensure that the BuildContext '
    'is appropriate for widgets built in the dialog.'
  ) Widget child,
  WidgetBuilder builder,
}
Copy the code
  • Builder: A component that creates popovers that create the desired interactive content
  • -sheldon: Well, if you get through here, you can implement the global. This is the key

ShowDialog -> showGeneralDialog -> Navigator. Of (context, rootNavigator: Of (context, rootNavigator: true).push() context is provided as an argument to navigater.of (context, rootNavigator: true).push

  • The comment to showGeneralDialog, which explains the key to closing the popover
/// The dialog route created by this method is pushed to the root navigator.
/// If the application has multiple [Navigator] objects, it may be necessary to
/// call `Navigator.of(context, rootNavigator: true).pop(result)` to close the
/// dialog rather than just `Navigator.pop(context, result)`.
///
/// See also:
///
///  * [showDialog], which displays a Material-style dialog.
///  * [showCupertinoDialog], which displays an iOS-style dialog.
Copy the code

Implement a simple popover

  • In demo floatingActionButton _incrementCounter event, the event is triggered to display a popover, the specific content can be combined with code annotations

  void _incrementCounter() {showDialog(context: context, Builder: (context) {// Scaffold is used to return the content displayed and can follow the themereturnScaffold(backgroundColor: color.transparent, // set body to Center(// Center to display child: Column (vertical layout mainAxisAlignment / / definition: mainAxisAlignment center, / / the spindle center layout, flutter under related introduction can search - the content of the UI children: < widgets > [/ / CircularProgressIndicator bring loading effect, need high Settings can be in with a layer of sizedbox wide, Settings can be wide high CircularProgressIndicator (), SizedBox (height: 10,), Text ('loading'RaisedButton(Child: Text('close dialog'),
                  onPressed: () {
                    print('close'); },),],);},);},);},); }); }Copy the code

It’s not the end, it’s just the beginning. Close the popover and hit the physical Back button to go back. You can close the window by calling navigator. of(context, rootNavigator: True). Pop (result). Modify the RaisedButton event content

RaisedButton(
  child: Text('close dialog'),
  onPressed: () {
    Navigator.of(context, rootNavigator: true).pop(); },),Copy the code

The popover can then be closed by button control

Access dio

When the interface request is triggered, the showDialog is called first to trigger the popover, and the interface request is completed to close the window

  • Dart implements GET interface request, adds interceptors, and accesses onRequest, onResponse, and onError functions. The pseudocode is as follows
import 'package:dio/dio.dart' show Dio, DioError, InterceptorsWrapper, Response;

Dio dio;

class Http {
  static Dio instance() {
    if(dio ! = null) {returndio; // instantiate dio} dio = new dio (); Dio. Interceptors. Add (InterceptorsWrapper(// handle data before interface request) onRequest: (options) {returnoptions; }, // Handle onResponse: (Response resp) {returnresp; }, // handle onError: (DioError error) {returnerror; },),);returndio; Static get(path) {static get(path) {returninstance().get(path); }}Copy the code
  • Dart implements the popover, dio calls loading. before on onRequest, and onResponse/onError calls loading. Complete the window with the following pseudocode
import 'package:flutter/material.dart'; Class Loading {static void before(text) {// display the popover before the request // showDialog(); } static voidcomplete() {// Close loading window when finished // Navigator. Of (context, rootNavigator:true).pop(); }} class Index extends StatelessWidget {final String text; Index({Key key, @required this.text}):super(key: key); @override Widget build(BuildContext context) {returnxxx; }}Copy the code

Pop-up critical analysis

context

Solve the context in showDialog, that is, can realize popover arbitrary calls, not limited to DIO requests. The Scaffold is not an arbitrary context. Only in that Scaffold can the Navigator object be found in Navigator. Of (context). (First contact, many times will feel the same context, why call of(context) error.)

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    print('main${Navigator.of(context)}'); / /!!!!!! Error here!!return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page')); }... } I/flutter (9137): Navigator operation requested with a context that does not include a Navigator. I/flutter ( 9137): The context used to push or pop routes from the Navigator must be that of a widget that is a I/flutter ( 9137): descendant of a Navigator widget. That is, it cannot be found in the MaterialApp.Copy the code

Let’s look in _MyHomePageState to see if there is a Navigator in the context object content when the build returns that Scaffold

class _MyHomePageState extends State<MyHomePage> {

  @override
  Widget build(BuildContext context) {
    print('home${Navigator.of(context)}'); // NavigatorState is printed properly#600dc(tickers: tracking 1 ticker)}... // Omit other content}Copy the code

So the context in the global popover, you need the context in that scaffold. When the project starts, the context is stored globally for showDialog use before the build returns the scaffold component for the first time. (There is no limit to returning the first time, as long as you save the context globally before calling showDiolog. It is up to you.) So far, we can solve the problem that when calling showDialog in DIO, context is often used incorrectly, which leads to error.

Extended analysis of the context encountered in conjunction with provide in the Flutter – UI. Connect. The context here returns the context of the provide instance, and then returns the context of the MaterialApp, which is also the context of the MaterialApp itself. This does not work with the Navigator object and ultimately saves the context provided by the Scaffold globally through the Provide data management setWidgetCtx when building the Scaffold.

Implement the global storage context

1 Store a context static variable in the loading class of the HTTP/load. dart file.

class Loading { static dynamic ctx; Static void before(text) {// Show popover before request // showDialog(context: CTX, Builder: (context) {//return Index(text:text);
	// );
  }

  static void complete() {// Close loading window when finished // navigator.of (CTX, rootNavigator:true).pop(); }}Copy the code

2 In main.dart, inject loading. CTX = context before _MyHomePageState build returns; For convenience of distinction, we use CTX to store

import 'package:flutter_loading/http/loading.dart'show Loading; . Class _MyHomePageState extends State<MyHomePage> {@override Widget build(BuildContext context) {print('home $context');
    print('home ${Navigator.of(context)}'); Loading.ctx = context; / / into the contextreturn. ; }Copy the code

Loading on DIO requests

This solves the key context point. Next, implement the interface interaction. Click the button, call the dio.get interface to pull the data, and call load.before () before onRequest; OnResponse calls Loading.complete() to close.

import 'package:flutter/material.dart'; class Loading { static dynamic ctx; Static void before(text) {// showDialog(context: CTX, Builder: (context) {returnIndex(text: text); }); } static voidcompleteOf (CTX, rootNavigator:true).pop(); }}Copy the code

Modify the content of DIO. In order to see the loading effect when the interface request is returned quickly, future. delayed is added to onResponse, which delays data return for 3s.

import 'package:dio/dio.dart' show Dio, DioError, InterceptorsWrapper, Response;
import 'loading.dart' show Loading;
Dio dio;

class Http {
  static Dio instance() {
    if(dio ! = null) {returndio; // instantiate dio} dio = new dio (); OnRequest: (options) {load.before (options) {load.before (options);'Is accelerating... ');
          returnoptions; }, // Handle onResponse: (Response resp) {// We added 3 seconds of future.delayed (Duration(seconds:) : 3), () { Loading.complete();returnresp; }); }, // handle onError: (DioError error) {returnerror; },),);returndio; Static get(path) {static get(path) {returninstance().get(path); }}Copy the code

Modify the contents of the _incrementCounter function to trigger an interface call via http.get

import 'package:flutter/material.dart';
import 'package:flutter_loading/http/loading.dart' show Loading;
import 'http/index.dart'show Http; . // Omit the code void_incrementCounter() {
	// Loading.before('loading... ');
    Http.get('https://raw.githubusercontent.com/efoxTeam/flutter-ui/master/version.json'); }... // Omit the codeCopy the code

Ok. You should see the following effect.

Concurrent requests are processed with loading

For concurrent requests, loading only ensures that one is currently running. The interface returns end. You only need to disable Loading when the last one is complete.

  • Set is used to manage concurrent request addresses. Set. Length controls the popover and close window.
  • Add LoadingStatus to check whether a popover already exists
  • Modify onRequest onResponse/onError into arguments
import 'package:flutter/material.dart';

Set dict = Set();
bool loadingStatus = false; class Loading { static dynamic ctx; static void before(uri, text) { dict.add(uri); / / in thesetDict. Length >= 2 ensures that there is an execution popover.if (loadingStatus == true || dict.length >= 2) {
      return ;
    }
    loadingStatus = true; ShowDialog (Context: CTX, Builder: (context) {returnIndex(text: text); }); } static void complete(uri) { dict.remove(uri); // All interfaces return with a popoverif (dict.length == 0 && loadingStatus == true) {
      loadingStatus = false; Of (CTX, rootNavigator:true).pop(); }}}Copy the code
http/index.dart

onReuest: Loading.before(options.uri, 'Is accelerating... ');
onReponse: Loading.complete(resp.request.uri);
onError: Loading.complete(error.request.uri );
Copy the code

Welcome everyone to communicate ~