Overall project construction

Upload projects to Gitee and Github

Learn basic

Based on Flutter super complete imitation Tencent animation, novel reading, douyin video project, rich features, suitable for learning and daily use, has a good project structure & relatively standard code

Flutter practice project (including integration testing, accessibility testing). Contains complete UI design diagrams, more realistic project exercises

The WanAndroid client based on Google Flutter supports Android and iOS. BLoC, RxDart, Internationalization, theme color, launch page, lead page!

Imitation netease cloud music App

Flutter is a complete open source project with rich features for learning and daily use

Awesome Flutter Project, 100% 100% restore of Flutter client. Home page, book, video, group, market and personal center, no pull

A library of Flutter UI components

Flutter is a microblogging app that contains a home page, video, discovery, messages and a personal hub module

About Flutter is very similar to Zhihu UI. It is very beautiful and smooth.

The novel, Flutter, supports iOS and Android

Flutter_Mall is a Flutter open source online marketplace application

Imitation netease Cloud music

Douyu livestream APP 🚀 Diversified Flutter open source project. Covers gift effects, gesture animation, barrage pool, raffle, fish bar, etc. (also provides server-side Mock interface)

Wechat_flutter version wechat, an excellent Flutter IM library!

Including more than 350 component usage, component inheritance diagram, more than 40 loading components, App upgrade, verification code, bullet screen, music subtitles 4 plug-ins, a small but complete App project

Create a project

flutter create luckincoffee
Copy the code

Run directly through the creation of VsCode

The plug-in

Blog.csdn.net/weixin_2887…

Plug-ins that the project will use

Bounced plug-in

# bounced
fluttertoast: ^ 8.0.7
Copy the code

The flutter pub get will be automatically enabled. Ios needs to run pod install. If you don’t mind using VSCode to run it again

Fit the screen

# Fit screen
flutter_screenutil: ^{latest version}
Copy the code

Language internationalization

VSCode also needs to install the plugin Flutter Intl

# localization
  flutter_localizations:
    sdk: flutter
Copy the code

(Cmd+Shift+P) >>Flutter Intl: Initialize

Video playback

Video UI frame chewie

# Video playback component
  video_player: ^ 2.1.6
Copy the code
cd ios && pod install
Copy the code

Github.com/befovy/fijk… — IjkPlayer encapsulates Bilibili (plays RTSP stream)

Similar to Dayjs

# similar to dayjs
day: ^ 0.7.1
Copy the code

Startup screen

# App startup screen
flutter_native_splash: ^ 1.2.0
Copy the code

Resource file + variable

  • configuration

    flutter_native_splash.yaml

    flutter_native_splash:
    # This package generates native code to customize Flutter's default white native splash screen
    # with background color and splash image.
    # Customize the parameters below, and run the following command in the terminal:
    # flutter pub run flutter_native_splash:create
    # To restore Flutter's default white splash screen, run the following command in the terminal:
    # flutter pub run flutter_native_splash:remove
    
    # color or background_image is the only required parameter. Use color to set the background
    # of your splash screen to a solid color. Use background_image to set the background of your
    # splash screen to a png image. This is useful for gradients. The image will be stretch to the
    # size of the app. Only one parameter can be used, color and background_image cannot both be set.
    color: "#42a5f5"
    #background_image: "assets/background.png"
    
    # Optional parameters are listed below. To enable a parameter, uncomment the line by removing
    # the leading # character.
    
    # The image parameter allows you to specify an image used in the splash screen. It must be a
    # png file.
    #image: assets/splash.png
    
    # The color_dark, background_image_dark, and image_dark are parameters that set the background
    # and image when the device is in dark mode. If they are not specified, the app will use the
    # parameters from above. If the image_dark parameter is specified, color_dark or
    # background_image_dark must be specified. color_dark and background_image_dark cannot both be
    # set.
    #color_dark: "#042a49"
    #background_image_dark: "assets/dark-background.png"
    #image_dark: assets/splash-invert.png
    
    # The android, ios and web parameters can be used to disable generating a splash screen on a given
    # platform.
    #android: false
    #ios: false
    #web: false
    
    # The position of the splash image can be set with android_gravity, ios_content_mode, and
    # web_image_mode parameters. All default to center.
    #
    # android_gravity can be one of the following Android Gravity (see
    # https://developer.android.com/reference/android/view/Gravity): bottom, center,
    # center_horizontal, center_vertical, clip_horizontal, clip_vertical, end, fill, fill_horizontal,
    # fill_vertical, left, right, start, or top.
    #android_gravity: center
    #
    # ios_content_mode can be one of the following iOS UIView.ContentMode (see
    # https://developer.apple.com/documentation/uikit/uiview/contentmode): scaleToFill,
    # scaleAspectFit, scaleAspectFill, center, top, bottom, left, right, topLeft, topRight,
    # bottomLeft, or bottomRight.
    #ios_content_mode: center
    #
    # web_image_mode can be one of the following modes: center, contain, stretch, and cover.
    #web_image_mode: center
    
    # To hide the notification bar, use the fullscreen parameter. Has no affect in web since web
    # has no notification bar. Defaults to false.
    # NOTE: Unlike Android, iOS will not automatically show the notification bar when the app loads.
    # To show the notification bar, add the following code to your Flutter app:
    # WidgetsFlutterBinding.ensureInitialized();
    # SystemChrome.setEnabledSystemUIOverlays([SystemUiOverlay.bottom, SystemUiOverlay.top]);
    #fullscreen: true
    
    # If you have changed the name(s) of your info.plist file(s), you can specify the filename(s)
    # with the info_plist_files parameter. Remove only the # characters in the three lines below,
    # do not remove any spaces:
    #info_plist_files:
    # - 'ios/Runner/Info-Debug.plist'
    # - 'ios/Runner/Info-Release.plist'
    
    # To enable support for Android 12, set the following parameter to true. Defaults to false.
    android12: true
    Copy the code
  • Generate resources

flutter pub pub run flutter_native_splash:create
Copy the code

Persistent storage tools

# Persistent library
  shared_preferences: ^ 2.0.6
Copy the code

Simplify tools for using Shared_Preferences – initialization is required

WidgetsFlutterBinding. The ensureInitialized () this must be added (ensure WidgetsFlutterBinding initialized, widgets and Flutter binding together)

void main() async {
  WidgetsFlutterBinding.ensureInitialized()
  await SpUtil.getInstance();
  runApp(MyApp());
}
Copy the code

Synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized

Shared_preferences are suitable for small applications, and Flutter also has a SQFLite database for persistence

Multithreading requires locking in order to ensure the program

# lock
  synchronized: ^ 3.0.0
Copy the code

When it comes to multithreading, you have to think about locking, data synchronization. (There is a variable a, when multiple threads access, must ensure that only one thread is reading/setting at a time)

Blog.csdn.net/Ani/article…

The realization principle of synchronized

All objects automatically contain a single lock (monitor) that is locked whenever any of its synchronized methods are called on the object. For a particular object, all its synchronized methods share the same lock, which can be used to prevent multiple tasks from accessing the object’s memory encoded at the same time.

Image cache

# image cache
  cached_network_image: ^ 3.0.0
Copy the code
cd ios && pod install
Copy the code

Record learning Tips

Segmentfault.com/a/119000002…

Blog.csdn.net/weixin_3367…

Version back

Foreword: after 2021/6/29 up a wave of Flutter version 2.2.2 here to think about how to back: [general installation in Mac – CD/Library/development/Flutter] version back blog.csdn.net/u013297881/… Git reset – hard b22742018b3edf16c6cadd7b76d9db5e7f9064b5 – this version 2.2.0, finally flutter doctor had “premise is that under the flutter folder”

Duration

Duration is often used in conjunction with a Timer

Create a timer with a delay of 2 seconds

 // defined by milliseconds
  Duration duration = new Duration(milliseconds: 2000);
  // By second definition
  Duration duration2 = new Duration(seconds: 2);
  
  // Create a timer
  Timer timer = Timer(duration, (){
  
  
  
    // delay callback
  });
Copy the code

Duration can also be used in conjunction with Future

Create a timer with a delay of 2 seconds

  // By second definition
  Duration duration = new Duration(seconds: 2);

  Future.delayed(duration,(){



    // delay callback
  });
Copy the code

animation

AnimatedOpacity(
  // Transition time
  duration: Duration(milliseconds: 1200), opacity: _opacity, child:... ,);Copy the code

Var and Dynamic and object

dynamic

The base type for all Dart objects, the type changes over time, but in most cases, not using it directly through the variables it defines will turn type checking off, which means dynamix x= ‘Hal’; x.foo(); This static type check does not return an error, but it crashes because x does not have a foo () method

dynamic a ='abc'; // It starts as a string
a = 123; // Then assign it an int value
a = true; // Then Boolean
Copy the code

var

If the variable is declared as var, the assigned type cannot be changed and the system automatically determines the type runtimeType

var b ='cde'; // b is a string whose type cannot be changed
b = 123; // This will not compile and cannot assign int to string variables
Copy the code

object

Is the base class for Dart objects, when you define: object o = XXX; You can call the toString() and hashCode() methods of O because Object provides them, but if you try to call O.Foo (), static type checking will run an error. The main difference between Dynamic and object is in static type checking

internationalization

Editor install plug-in

VsCode install the plugin for Flutter Intl

Project installation library

# localization
  flutter_localizations:
    sdk: flutter
Copy the code

Initialize the

VsCode Shortcut (Cmd+Shift+P)

>Flutter Intl: Initialize– return to the carriage

configuration

  1. lib/I1On/intl_en.arb
{
  "Chats": "Chats"."Contacts": "Contacts"."Discover": "Discover"."Me": "Me"
}
Copy the code
  1. lib/I1On/intl_zh.arb
{
  "Chats": "WeChat"."Contacts": "Address book"."Discover": "Discovered"."Me": "我"
}
Copy the code

Lib /generated/* Internal files will be compiled with the above two files

  1. Entrance to the configuration
MaterialApp(
    / /... omit
    locale:
        locale.locale, // Locale('en', 'CN') or Locale('en', 'US')
    localizationsDelegates: [
      GlobalCupertinoLocalizations.delegate,
      GlobalMaterialLocalizations.delegate,

      /// Text direction and so on
      GlobalWidgetsLocalizations.delegate,
      // Intl plug-in (install required)
      S.delegate,
    ],
    supportedLocales: S.delegate.supportedLocales,
    / /... omit
)
Copy the code

Locale. Locale is provided by the Provider

  1. Provider
import 'dart:ui';

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_learn_awesome/utils/storage.dart';

class LocaleProvider extends ChangeNotifier {
  Locale? get locale {
    final String locale = SpUtil.getString('locale')??' ';
    switch (locale) {
      case 'zh':
        return Locale('zh'.'CN');
      case 'en':
        return Locale('en'.'US');
      default:
        return Locale('zh'.'CN'); }}void setLocale(String locale) {
    SpUtil.putString('locale', locale); notifyListeners(); }}Copy the code

SpUtil. GetString is the persistence tool

Dark mode

Two properties of the MaterialApp need to be set at the same time: darkTheme and Theme. DarkTheme indicates the theme in dark mode of the system, and theme indicates the theme in normal mode

IOS development – dark/light mode switch simulator – www.jianshu.com/p/8e0e4501d…

So there are two scenarios:

  1. The phone is in normal mode — forces the APP to be in dark mode
  2. The phone is in dark mode — forces the app to be in normal mode

So you need three options: Dark mode, light mode, and follow system

Keywords extension

Add new member functions to existing classes and add new functions to them

extension ParseNumbers on String {
  int parseInt() {
    return int.parse(this);
  }
  double parseDouble() {
    return double.parse(this); }}Copy the code

In the Extension function, we can use this to access other methods existing in string. This refers to the current String instance

PageView

PageView general users need to slide a small number of pages to switch the scene, but the overall use is very flexible, often used to switch Tab pages or Banner activities

PageView({
    Key? key,
    this.scrollDirection = Axis.horizontal,// Page sliding direction (horizontal/vertical)
    this.reverse = false.// Whether to reverse -- default false
    PageController? controller,// Page controller
    this.physics,// Slide --NeverScrollablePhysics --BouncingScrollPhysics --NeverScrollablePhysics BouncingScrollPhysics is the default interaction for iOS --FixedExtentScrollPhysics is the default interaction for iOS, UIDatePicker.
    this.pageSnapping = true.// Whether to scroll the entire page, the default is true.
    this.onPageChanged,// Page switch callback
    List<Widget> children = const <Widget>[],/ / child component
    this.dragStartBehavior = DragStartBehavior.start,// Select "down" and "start", the default is "start. down" indicates that the first finger is pressed, while "start" indicates that the finger is dragged
    this.allowImplicitScrolling = false.// For visually impaired people, the default is FASle
    this.restorationId,/ / page ID
    this.clipBehavior = Clip.hardEdge,// Clip.hardEdge clipping without anti-aliasing, clipping slower than None mode but faster than other methods clip. antiAlias Clipping and anti-aliasing for a smoother appearance. Cutting speed is faster than antiAliasWithSaveLayer, slower than hardEdge Clip. AntiAliasWithSaveLayer clips with anti-aliasing, and immediately after editing save saveLayer Clip. None is not cut
    this.scrollBehavior,// The default returns BouncingScrollPhysics and ClampingScrollPhysics effects specific to the platform.
  })
Copy the code
PageView(
  scrollDirection: Axis.horizontal,            // Switch the page direction
  physics: BouncingScrollPhysics(),         // Physical sliding effect
  reverse: true.// Whether to reverse the order
  controller: PageController(                  / / controller
    initialPage: 1.// Initial page index, first page is 0
    keepPage: false.// Whether to record the current page as the starting page for the next open
    viewportFraction: 1.0.// Display the scale
    ),
  onPageChanged: (int index){                // The function triggered when the page is switched
      setState(() {
          currentSelectIndex = index;
        });
  },
  pageSnapping: false.// Set to false when customizing scrollbars
  dragStartBehavior : DragStartBehavior.start,     // Start is smoother, down is more sensitive
  allowImplicitScrolling: false, 
  restorationId:'pageID', 
  clipBehavior: Clip.hardEdge                // Mask effect
  children: [                                               // Part of the page
    SizedBox(
      width: width,
      height: height,
      child: Container(
        color: Colors.amber,
          child:Text('Swiper 1')
      ),
    ),
    SizedBox(
      width: width,
      height: height,
      child: Container(
          color: Colors.cyan,
          child:Text(' Swiper 2'))))Copy the code

PageView provides a convenient.Builder () constructor for a large number of dynamic or similar widgets, similar to the ListView.Builder () approach

Raw pointer events (usually touch events on mobile devices) + gesture recognition (GestureDetector, GestureRecognizer)

When a pointer is pressed, Flutter performs a Hit Test on the application to determine which widgets exist at the point where the pointer touches the screen. The pointer pressing event (and subsequent events of the pointer) are then distributed to the innermost component found by the Hit Test. From there, Events bubble up the component tree. These events are distributed from the innermost component to all components along the path to the root of the component. This is similar to the event bubble mechanism of browsers in Web development, but there is no mechanism to cancel or stop the “bubble” process in Flutter. Note that only components that pass the hit test can fire events

Primitive pointer event

At the mobile end, the original pointer event model of UI systems on all platforms is basically the same: that is, a complete event is divided into three stages: finger press, finger move, and finger lift.

There are four types of pointer events: PointerDownEvent, PointerMoveEvent, PointerUpEvent, and PointerCancelEvent

High-level gestures (click, long press, swipe, swipe, zoom, rotate) are based on these raw events

Needle press events (and subsequent events in the pointer) is then distributed to the hit test found that the internal components, and then from there, the event will be bubbling up in the component tree, these events will be distributed to the component from the internal components of the path of the roots of all the components, the browser and Web development event bubbling mechanism similar to that of But there is no mechanism in Flutter to cancel or stop the bubbling process, whereas browser bubbling can be stopped. Note that only components that pass the hit test can fire events.

const Listener({
    Key? key,
    this.onPointerDown,// called when the finger is pressed
    this.onPointerMove,// called when the finger moves
    this.onPointerUp,// call when the finger is lifted
    this.onPointerHover,// There is no trigger down-- change
    this.onPointerCancel,// called when the finger slips out of the listening range, if down has already been called
    this.onPointerSignal,// called when the finger is first placed
    this.behavior = HitTestBehavior.deferToChild,/ / how to behave during hit testing / / child widgets to the click event -- HitTestBehavior. DeferToChild said child widget will hit test one by one, here the parent widget will also receive this event, Hittestbehavior. opaque indicates that the entire child in the Listener responds to the event. This parameter is not accurate to a widget. Will respond to an area HitTestBehavior. Translucent said viewing area or at the bottom of the widget will be response to
    Widget? child,/ / child component
  })
Copy the code

The sample

Listener(
  child: Container(
    alignment: Alignment.center,
    color: Colors.blue,
    width: 300.0,
    height: 150.0, child: Text(_event? .toString()??"",style: TextStyle(color: Colors.white)),
  ),
  onPointerDown: (PointerDownEvent event) => setState(()=>_event=event),
  onPointerMove: (PointerMoveEvent event) => setState(()=>_event=event),
  onPointerUp: (PointerUpEvent event) => setState(()=>_event=event),
),
Copy the code

Pointer information:

  • positionIt is the offset of the mouse relative to the global coordinates
  • delta: The distance between two pointermoveEvents
  • pressure: Press force, this attribute is more meaningful if the phone screen supports pressure sensor (such as iPhone 3D Touch), if the phone does not support, it is always 1
  • orientation: Direction of pointer movement, is an Angle value

. To expand the

Gesture recognition GestureDetector

There is also a component called InkWell that works just like the GestureDetector component

  1. They all provide many common features such as onTap, onLongPress, etc. The main difference is that GestureDetector provides more controls, such as dragging, on the other hand it does not include the ripple effect of clicking like InkWell does.

  2. You can use either of them according to your needs, you want the ripple effect to use InkWell, need more control should use GestureDetector, or even a combination of the two.

GestureDetector is a functional component for gesture recognition, through which we can recognize various gestures. GestureDetector is actually a semantic encapsulation of pointer events. Next, we’ll look at various gesture recognition in detail.

  • Click, double click, and long press
class GestureDetectorTestRoute extends StatefulWidget {
  @override
  _GestureDetectorTestRouteState createState() =>
      new _GestureDetectorTestRouteState();
}

class _GestureDetectorTestRouteState extends State<GestureDetectorTestRoute> {
  String _operation = "No Gesture detected!"; // Save the event name
  @override
  Widget build(BuildContext context) {
    return Center(
      child: GestureDetector(
        child: Container(
          alignment: Alignment.center,
          color: Colors.blue,
          width: 200.0, 
          height: 100.0,
          child: Text(_operation,
            style: TextStyle(color: Colors.white),
          ),
        ),
        onTap: () => updateText("Tap"),/ / click
        onDoubleTap: () => updateText("DoubleTap"), / / double
        onLongPress: () => updateText("LongPress"), / / long press)); }void updateText(String text) {
    // Update the event name displayedsetState(() { _operation = text; }); }}Copy the code

Note: When listening for both onTap and onDoubleTap events, there will be a delay of about 200 milliseconds when the user triggers the TAP event. This is because after the user clicks, it is likely to click again to trigger the double click event, so the GestureDetector will wait for some time to determine whether it is a double click event. There is no delay if the user only listens for onTap events (no onDoubleTap).

  • Drag, slide

The GestureDetector makes no distinction between drag and slide events; they are essentially the same. The GestureDetector will use the origin of the component to be monitored (upper left corner) as the origin of the gesture. Gesture recognition will start when the user presses his finger on the component to be monitored

class _Drag extends StatefulWidget {
  @override
  _DragState createState() => new _DragState();
}

class _DragState extends State<_Drag> with SingleTickerProviderStateMixin {
  double _top = 0.0; // Offset from the top
  double _left = 0.0;// Offset to the left

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: <Widget>[
        Positioned(
          top: _top,
          left: _left,
          child: GestureDetector(
            child: CircleAvatar(child: Text("A")),
            // This callback is triggered when the finger is pressed
            onPanDown: (DragDownDetails e) {
              // Print the position of the finger press (relative to the screen)
              // When the user presses, this property is the offset of the user's pressed position relative to the origin (upper left corner) of the screen (not the parent component).
              print("User finger presses:${e.globalPosition}");
            },
            // This callback is triggered when a finger is swiped
            // Multiple Update events are triggered when the user slides on the screen. Delta refers to the offset of the slide of an Update event
            onPanUpdate: (DragUpdateDetails e) {
              // When the user swipes, the offset is updated and rebuilt
              setState(() {
                _left += e.delta.dx;
                _top += e.delta.dy;
              });
            },
            onPanEnd: (DragEndDetails e){
              // Prints the speed on the x and y axes at the end of the slide
              // This property represents the sliding speed when the user lifts the finger (including x and Y axes)
              print(e.velocity); },),)],); }}Copy the code

Drag in one direction – just define and set one direction

  • The zoom

GestureDetector can listen for zoom events

class _ScaleTestRouteState extends State<_ScaleTestRoute> {
  double _width = 200.0; // Change the width of the image to achieve the zoom effect

  @override
  Widget build(BuildContext context) {
   return Center(
     child: GestureDetector(
        // Specify the width and height to be adaptive
        child: Image.asset("./images/sea.png", width: _width),
        onScaleUpdate: (ScaleUpdateDetails details) {
          setState(() {
            // The zoom is between 0.8 and 10 times
            _width=200*details.scale.clamp(8..10.0); }); },),); }}Copy the code
 GestureDetector({
    Key? key,
    this.child,

    // # Tap
    this.onTapDown,// Click when pressed
    this.onTapUp,// Click to lift
    this.onTap,/ / click
    this.onTapCancel,// Click Cancel

    // # onSecondaryTap is called when the user touches the screen with the 'secondary button' (secondary button or secondary button -- called for less important operations).
    // # onSecondaryTapDown is called when the user starts to contact the screen using the 'secondary button' (secondary button or secondary button -- for less important operations).
    // # onSecondaryTapUp is called when the user stops the touch screen and clicks' secondary 'behind the scenes.
    // # onSecondaryTapCancel is called when the user touches the screen with the 'secondary button' (secondary button or called secondary button -- for less important operations) but does not complete the 'secondary' click.
    // https://api.flutter.dev/flutter/widgets/GestureDetector/onSecondaryTapDown.html
    this.onSecondaryTap,
    this.onSecondaryTapDown,
    this.onSecondaryTapUp,
    this.onSecondaryTapCancel,

    // # A 'tertiary button' click touches the screen at a specific location.
    // # onTertiaryTapUp is called when the tap is complete, onTertiaryTapCancel is called when the tap is not.
    // https://api.flutter.dev/flutter/widgets/GestureDetector/onTertiaryTapUp.html
    this.onTertiaryTapDown,
    this.onTertiaryTapUp,
    this.onTertiaryTapCancel,

    / / # double-click
    this.onDoubleTapDown,// Double click when pressed (second click)
    this.onDoubleTap,// Double-click when pressed
    this.onDoubleTapCancel, // Double click is cancelled

    / / # long press
    this.onLongPress,/ / long press
    this.onLongPressStart,// Long press to start
    this.onLongPressMoveUpdate,// Long press to move
    this.onLongPressUp,// Long press to lift
    this.onLongPressEnd,// Long press to end

    // Same as above -- a click becomes a long press
    this.onSecondaryLongPress,
    this.onSecondaryLongPressStart,
    this.onSecondaryLongPressMoveUpdate,
    this.onSecondaryLongPressUp,
    this.onSecondaryLongPressEnd,

    // # vertical slide
    this.onVerticalDragDown, // Slide vertically down
    this.onVerticalDragStart,// Vertical slide begins
    this.onVerticalDragUpdate,// When vertical sliding is going on
    this.onVerticalDragEnd,// Vertical slide ends
    this.onVerticalDragCancel,// Vertical slide cancels

    // # slide horizontally
    this.onHorizontalDragDown,// Slide horizontally down
    this.onHorizontalDragStart,// Horizontal slide begins
    this.onHorizontalDragUpdate,// When horizontal sliding is going on
    this.onHorizontalDragEnd,// Horizontal slide ends
    this.onHorizontalDragCancel,// Slide horizontally to cancel

    / / press
    this.onForcePressStart,// Press pressure to start
    this.onForcePressPeak,// Press the pressure peak
    this.onForcePressUpdate,// Change by pressure (in progress)
    this.onForcePressEnd,// Press pressure to end

    // # press move
    this.onPanDown,OnScale, onVerticalDrag, onHorizontalDrag cannot be used together with onScale, onVerticalDrag, onHorizontalDrag
    this.onPanStart,// Press start move, cannot be used with onScale, onVerticalDrag, onHorizontalDrag
    this.onPanUpdate,// Press move, cannot be used with onScale, onVerticalDrag, onHorizontalDrag
    this.onPanEnd,// Press move end, cannot be used with onScale, onVerticalDrag, onHorizontalDrag
    this.onPanCancel,// Press Move to cancel, cannot be used with onScale, onVerticalDrag, onHorizontalDrag

    / / # zoom
    this.onScaleStart,// Zoom start cannot be used with onPan, onVerticalDrag, onHorizontalDrag
    this.onScaleUpdate,// Can not be used with onPan, onVerticalDrag, onHorizontalDrag at the same time
    this.onScaleEnd,// End of zoom cannot be used together with onPan, onVerticalDrag, onHorizontalDrag

    this.behavior,// How the gesture detector should behave during a hit test. Enumeration values of HitTestBehavior: deferToChild, Opaque, always ----
    // deferToChild: Child components are tested one by one, and if any of the child components pass, the current component passes, meaning that if a pointer event is applied to a child, its parent component must receive the event as well
    Opaque: In a hit test, treat the current component as opaque (even if it is transparent). The result is that the entire area of the current Widget is the click area
    // A component always tests both within its own boundaries and in the bottom viewable region when clicked on the component's clear region, meaning that both top and bottom receive events when clicked
    this.excludeFromSemantics = false.// // whether to exclude these gestures from the semantic tree.
    this.dragStartBehavior = DragStartBehavior.start,//// Determines how to handle the drag start behavior. Enumeration values: down, start- whichever is start or down
  })
Copy the code
  • Click on the area
Listener(
    child: ConstrainedBox(
        constraints: BoxConstraints.tight(Size(300.0.150.0)),
        child: Center(child: Text("Box A"))),//behavior: HitTestBehavior.opaque,
    onPointerDown: (event) => print("down A"))Copy the code

Originally, the click area of this section was the text part — after untying behavior: HitTestBehavior. Opaque 300×150

OnScale, onVerticalDrag, onHorizontalDrag, onScale, onPan, onPan, onVerticalDrag, OnHorizontalDrag cannot be used at the same time

GestureRecognizer

GestureDetector uses one or more Gesturerecognizers internally to recognize gestures. Gesturerecognizers convert the original pointer event to semantic gestures via a Listener. GestureDetector can receive a child widget directly. GestureRecognizer is an abstract class. GestureRecognizer is a subclass of GestureRecognizer. Flutter implements a rich set of gesture recognizers that can be used directly.

Let’s say we’re adding click event handlers to different parts of a RichText, but TextSpan isn’t a widget, so we can’t use the GestureDetector, but TextSpan has a recognizer property, It can accept a GestureRecognizer.

TextSpan(
    text: "Click on me to change color.",
    style: TextStyle(
        fontSize: 30.0, color: _toggle ? Colors.blue : Colors.red ), recognizer: _tapGestureRecognizer .. onTap = () { setState(() { _toggle = ! _toggle; }); },)Copy the code

Gestures conflict

Gesture recognition in Flutter introduces the concept of an Arena, which literally means “Arena”. Each GestureRecognizer is a “competitor”, and when a sliding event occurs, They all have to compete for the right to handle this event in the “arena”, and only one “competitor” will win (WIN).

For example, suppose you have a ListView whose first child is also a ListView. If you slide the child ListView now, will the parent ListView move? The answer is no, only the sublistView will move, because the subListView will win and get the right to handle the sliding event.

Example:

Let’s take the drag gesture as an example, identify both horizontal and vertical drag gestures. When the user presses the finger, competition (horizontal and vertical) will be triggered. Once a direction “wins”, it will move in that direction until the end of the drag gesture

import 'package:flutter/material.dart';

class BothDirectionTestRoute extends StatefulWidget {
  @override
  BothDirectionTestRouteState createState() =>
      new BothDirectionTestRouteState();
}

class BothDirectionTestRouteState extends State<BothDirectionTestRoute> {
  double _top = 0.0;
  double _left = 0.0;

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: <Widget>[
        Positioned(
          top: _top,
          left: _left,
          child: GestureDetector(
            child: CircleAvatar(child: Text("A")),
            // Drag events verticallyonVerticalDragUpdate: (DragUpdateDetails details) { setState(() { _top += details.delta.dy; }); }, onHorizontalDragUpdate: (DragUpdateDetails details) { setState(() { _left += details.delta.dx; }); },),)],); }}Copy the code

Because gesture contests end with only one winner, conflicts can occur when there are multiple gesture recognizers

GestureDetector(
      child: CircleAvatar(child: Text("A")), // Widgets to drag and click
      onHorizontalDragUpdate: (DragUpdateDetails details) {
        setState(() {
          _left += details.delta.dx;
        });
      },
      onHorizontalDragEnd: (details){
        print("onHorizontalDragEnd");
      },
      onTapDown: (details){
        print("down");
      },
      onTapUp: (details){
        print("up"); },)Copy the code

Instead of printing up,onHorizontalDragEnd and onTapUp collide, but since it’s in drag semantics,onHorizontalDragEnd wins, so “onHorizontalDragEnd” is printed instead of up

If our code logic, for the finger press and lift is strongly dependent on, such as a round figure components, we hope that the finger press, suspension wheel, and lift up the back wheel, but because of shuffling figure component itself may have been dealt with drag gestures (support manual sliding switch), even may also support the zoom gestures, At this time, if we use onTapDown and onTapUp externally to listen, it will not work

What should we do? Listen for the original pointer event using a Listener:

Positioned(
  top:80.0,
  left: _leftB,
  child: Listener(
    onPointerDown: (details) {
      print("down");
    },
    onPointerUp: (details) {
      / / triggers
      print("up");
    },
    child: GestureDetector(
      child: CircleAvatar(child: Text("B")),
      onHorizontalDragUpdate: (DragUpdateDetails details) {
        setState(() {
          _leftB += details.delta.dx;
        });
      },
      onHorizontalDragEnd: (details) {
        print("onHorizontalDragEnd"); },),),)Copy the code

Gesture conflict is only at gesture level, and gesture is semantic recognition of the original pointer. Therefore, when encountering complex conflict scenes, the Listener can directly identify the original pointer event to solve the conflict

Ignore PointerEvent

If we don’t want a subtree to respond to a PointerEvent, we can use IgnorePointer and AbsorbPointer, both of which prevent the subtree from receiving pointer events

Difference: The AbsorbPointer itself participates in the hit test, whereas IgnorePointer itself does not. Generally, the AbsorbPointer itself can receive pointer events (but its sub-tree does not), whereas IgnorePointer cannot

Listener(
  child: AbsorbPointer(
    child: Listener(
      child: Container(
        color: Colors.red,
        width: 200.0,
        height: 100.0,
      ),
      onPointerDown: (event)=>print("in"),
    ),
  ),
  onPointerDown: (event)=>print("up"),Copy the code
  1. When the Container is clicked, it does not respond to the pointer event because it is on the AbsorbPointer tree: no responsein
  2. The AbsorbPointer itself can receive pointer events, so it outputs “up”.
  3. If you replace the AbsorbPointer with IgnorePointer, neither is output

MicroTask queues and Event queues

Dart’s event loop is basically the same as JavaScript’s. There are two queues in the loop. One is the MicroTask queue and the other is the Event queue.

MicroTask queues are used for very short internal actions that need to be executed asynchronously, and these actions need to be completed before execution is handed back to the Event queue to run.

  • MicroTask queue (MicroTask queue)

A microtask queue represents an asynchronous task that will be completed within a short period of time. It has the highest priority, higher than the Event queue, and can hog the event loop as long as there are tasks in the queue. The tasks added to the MicroTask Queue are primarily generated within Dart

  • The Event queue

The Event queue contains all foreign events: I/O, Mouse Events (gestures), flows, futures, Drawing Events (drawing), timers(timers), and messages between isolate.

process

In each event loop, Dart checks the first MicroTask Queue to see if there are any executable tasks, and if not, processes the subsequent event queue.

Dart’s event loop runs according to the following rules:

  1. First, all the microtasks in the microtask queue are processed. (Dart needs to process all microtasks before processing the event queue.)
  2. After all the microtasks are done. Fetch an event from the event queue.
  3. Return to the microtask queue and continue the loop
  4. If at any point there are eight microtasks in the microtask queue and two events in the event queue, Dart will first process all eight microtasks and then remove one event from the event queue, and then return to the microtask queue to see if there are any outstanding microtasks

Microtask queues are processed all at once, and event queues are processed one at a time

Isoalte “Multi-threading”

Not supported on the Web – using worker

Dart is a single-threaded execution model. –Dart is a single-threaded execution mode, but Dart has another concept called Isoalte[isolation]. Isoalte is a high-level encapsulation of threads that represents an execution environment. Memory is not shared between different execution environments (Isolate).

The independent space of the ISOLATE

/// Define a global variable a=10
int a = 10;

void main() {
  /// Start a ISOLATE, call func and pass in the parameter '123'
  Isolate.spawn(func, 123);
  // After ten seconds of sleep
  sleep(Duration(seconds: 10));
  Print the value of a
  print(Isolate.current.debugName + ' a = $a');
}

void func(int count) {
  /// The received parameter is assigned to a
  a = count;
  /// Print the value of a
  print(Isolate.current.debugName + ' func中a = $a');
}
Copy the code

run

Flutter: func func where a =123
flutter: main a = 10
Copy the code

No matter how long sleep is set, the two printed A values are not the same. Here we can prove that A is in the two ISOLATE and has its own independent memory space, which does not affect each other. Another benefit is that you don’t have to worry about multi-threaded resource grabs.

Interact data between isolate

Data interaction between the isolate is achieved through ports.

int a = 10;
void main() {
  test();
}

void test() async {
  // Create a port
  ReceivePort port = ReceivePort();
  // Create the ISOLATE instance. Note that we need "await", but this "await" will not affect synchronous code execution.
  Isolate iso = await Isolate.spawn(func, port.sendPort);
  // Listen for data
  port.listen((message) {
    // Received 1000 assignment
    a = message;
    print('listen a = $a');
    // Close the port
    port.close();
    / / close the iso
    iso.kill();
  });

  sleep(Duration(seconds: 10));
  print('a = $a');
}

void func(SendPort send) {
  // You can access the main thread
  send.send(1000);
}
Copy the code

Run (jump out together after sleeping for 10 seconds)

flutter: a = 10
flutter: listen a = 1000
Copy the code

compute

The ISOLATE is relatively low-level, so it’s a lot of code to write. Compute encapsulates the ISOLATE, making it easier to use. For example, the callback method in compute returns a value, but the callback method in isolate.spawn does not.

int a = 10;
void main() {
  computeDemo();
}

void computeDemo() async {
  print('External code 1');

  a = await compute(func2, a);
  print('a = $a');

  sleep(Duration(seconds: 1));
  print('External code 2');
}

// Returns a value
int func2(int count) {
  return 10000;
}
Copy the code

run

Flutter: external code 1 FLUTTER: A = 10000 Flutter: external code 2Copy the code

example

class TestWidget extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    returnTestWidgetState(); }}class TestWidgetState extends State<TestWidget> {
  int _count = 0;
 
  @override
  Widget build(BuildContext context) {
    return Material(
      child: Center(
        child: Column(
          children: <Widget>[
            Container(
              width: 100,
              height: 100,
              child: CircularProgressIndicator(),
            ),
            FlatButton(
                onPressed: () async {
                  _count = countEven(1000000000);
                  setState(() {});
                },
                child: Text(
                  _count.toString(),
                )),
          ],
          mainAxisSize: MainAxisSize.min,
        ),
      ),
    );
  }
 
  // Count the number of even numbers
  static int countEven(int num) {
    int count = 0;
    while (num > 0) {
      if (num % 2= =0) {
        count++;
      }
      num-; }returncount; }}Copy the code

UI contains two parts, a continuously in the progress indicator, a button, when click the button to find the number of smaller than a positive integer n the even number, we can see, this time was very smooth, when I click on the button to calculate the UI caton, why caton, Because our calculation is in the UI thread by default, when we call countEven, the calculation takes time, and during this period, the UI has no chance to call the refresh, so it will be stuck. After the calculation is completed, the UI will resume normal refresh.

What if I make this asynchronous?

  static Future<int> asyncCountEven(int num) async{
    int count = 0;
    while (num > 0) {
      if (num % 2= =0) {
        count++;
      }
      num-; }return count;
  }

  _count = await asyncCountEven(1000000000);
Copy the code

Still stuck, indicating that asynchrony can’t solve the problem, why? Because we’re still doing it in the same UI thread, asynchronous just means I can run something else first, and I’ll come back when I get something, but remember, we’re still doing it in the same UI thread, we’re still blocking the UI refresh, asynchronous is just doing concurrent operations in the same thread.

  • Compute is used to optimize the time-consuming operation

Because the Isolate in DART is heavyweight and the transfer of UI threads and data within the Isolate is complex, flutter encapsulates a lightweight compute operation in the Foundation library in order to simplify user code. Let’s look at Compute first and then the Isolate

In order to use compute, we have to pay attention to two things: first, the function that we run in compute must be a top-level function or a static function, and second, compute passes only one parameter and returns only one value

_count = await compute(countEven, 1000000000);
Copy the code
  • Isolate optimization

There’s no way that compute can return multiple times. There’s no way that compute can continuously pass through the value. Every time you call it, you create a new isolation, which can be counterproductive if you call it too many times. In some services, we can use Compute, but in others, we can only use THE Isolate provided by DART.

/// The new function
  static Future<dynamic> isolateCountEven(int num) async {
    1. Create a Port to communicate with the isoLate environment
    final response = ReceivePort();
    / / 2. Create a separate isolate and provides for acknowledgement sendPort, receivePort. SendPort is a sendPort send current receive message
    await Isolate.spawn(countEvent2, response.sendPort);
     //5. Now you need a sendPort to send messages to isolate
    final sendPort = await response.first;
    final answer = ReceivePort();
    // Send a message to countEvent2-- the first of the array is a new ac port
    sendPort.send([answer.sendPort, num]);
    // A new result message will be received
    return answer.first;
  }
  // 3. Create a function called countEvent2 for execution on the new ISOLATE
  static void countEvent2(SendPort port) {
    //4. Now provide the main ISOLATE with sendPort to send messages to the sub-isolate
    final rPort = ReceivePort();
    / / communication
    port.send(rPort.sendPort);
    // Listen for messages sent from rPort
    rPort.listen((message) {
      // This side sends new messages through a new port
      final send = message[0] as SendPort;
      final n = message[1] as int;
      send.send(countEven(n));
    });
  }

  _count = await isolateCountEven(1000000000);
Copy the code
  • Use the thread pool LoadBalancer

Foreword: for us, is actually to use multithreading as a kind of computing resources. We can reduce the burden of UI threads by creating a new ISOLATE that calculates heavy work. But at what cost?

  • Time: The creation and destruction of the ISOLATE will take about 50-150ms
  • Space: The Isolate is actually quite heavy, every time we create a new Isolate we need at least 2MB space or more, depending on what we are using it for.
  • OOM risk (memory overflow) : If we wanted to return 2GB of data, on the iPhone X (3GB OF RAM), we would not be able to deliver messages.

Solution (thread pool) : initialize to there. We can use it when we need it.

Juejin. Cn/post / 686816… — Learn

In fact, the DART team has written a very practical package for us that includes LoadBalancer

isolate: ^ 2.0.2
Copy the code

Is there a question mark on this library? Because This package has been replaced, and will no longer be maintained.

www.jianshu.com/p/07b19f475…

The Future with the Stream

Before we look at this, let’s take a look at the previous MicroTask queue and Event queue, as well as multi-threaded ISOLATE.

The Dart code base has a large number of functions that return Future or Stream objects. These functions are asynchronous, and they return before time-consuming operations have completed rather than waiting for time-consuming operations to complete. The async and await keywords are used to implement asynchronous programming and make your code look synchronous

Future

You can obtain the result of a Future execution in two ways:

// Get data from the network
Future<String> getUserName() =>
    Future.delayed(Duration(seconds: 1), () = >'ShuSheng007');
Copy the code
  1. Use async and await
Future<void> main(){
  try {
    String name = await getUserName();
    print('Start printing names');
    print('my name is :$name');
  } catch (e) {
    print(e);
  } finally{}} ------ Run for one second after simultaneously printing flutter: start printing the name flutter: my nameis :ShuSheng007
Copy the code
  1. A Future API
getUserName()
    .then((value) => print(value))
    .catchError((e,stackTrace) =>print('Exception:$e StackTrace:$stackTrace'))
    .whenComplete(() => print('Do it anyway')); --> Run flutter: ShuSheng007 flutter: execute flutter anywayCopy the code

Future anonymous functions

 await Future(() {
    for (int i = 0; i < 100000000; i++) {
      _data = 'Network data';
    }
    print(Data = '2 - end$_data');
  });
Copy the code

Order of execution:

String _data = '0';

void main() {
  getData2();
  print('4- Do something else ');
}

void getData2() async {
  print(1 - start data = '$_data');

  // 1. The following operations must be asynchronous to use await
  / / 2. The current function must be asynchronous
  await Future(() {
    for (int i = 0; i < 100000000; i++) {
      _data = 'Network data';
    }
    print(Data = '2 - end$_data');
  });

  print(Data = '3 - end$_data');
}
Copy the code

–>

Flutter: 1- start data=0 2- End data= network data // Then print the last 3 flutter: 3- end data= network dataCopy the code

Future.value()

Creates a Future that returns the specified value, and returns the Future.

void main() {
  futureValueTest();
  print('Doing things');
}

void futureValueTest() async {
  var future = await Future.value('cool');
  print(future); } Run result: flutter: do something flutter: awesomeCopy the code

Future.delay()

Create a deferred Future and return the Future object.

void main() {
  futterDelayTest();
  print('4- Do something else ');
}

void futterDelayTest() {
  Future.delayed(Duration(seconds: 3), () {
    print("Delayed execution of 3 seconds"); }); } runtime result: flutter:4- Do something else flutter: delay3Seconds to performCopy the code

The delay operation implemented in the Future is realized through Timer. In actual development, if it is only a simple delay operation, Timer is recommended

Timer Timer = new Timer(Duration(seconds: 3), () {print(" delay 3 seconds "); });Copy the code

Future Api

  • Then: Registers the callback to be called when a Future completes and returns a Future object

    1. If a Future has more than one then(), they are also executed synchronously in the order of the links and share an Event loop
    2. Then () has a higher queue priority than Future’s default, and then() is executed as soon as the Future body completes execution
void main() {
  Future(() => print('A')).then((value) => print(End of the 'A'));
  Future(() => print('B')).then((value) => print(End of the 'B')); } Result: flutter: A flutter: A end flutter: B flutter: B endCopy the code
  • catchError: Registers a callback that catches a Future error and returns a Future object

CatchError used after THEN is different from catchError used before THEN

then((value) {
  print('Take care of business');
}).catchError((e) {
  print('Catch exception'); }); Result: FLUTTER: catch exception catchError((e) {print('Catch exception');
}).then((value) {
  print('Take care of business'); }); Result: FLUTTER: catch exception flutter: handle business Future(() {throw Exception('error1');
}).catchError((e) {
  print(e);
  throw Exception('error2');
}).then((e)=>{
  print(e);
}, onError: (error) {
  print(error); }); Result: FLUTTER: Exception: error1 Flutter: Exception: error2Copy the code
  • whenCompleteThe Future is always called after completion, whether due to error or after normal execution, and returns a Future object
Future(() {
  return 'No errors';
}).then(print).
//catchError(print
catchError(print).
whenComplete(() => print('whenComplete')); Result: flutter: no error flutter: whenCompleteCopy the code

Future advanced

  • Future.wait()

After both network requests A and B have completed, code C can be executed using future.wait (). In short, all futures return normal results. Future returns a set of all the results of the specified Future (like promise.all).

var future1 = new Future(() => 'task 1');
var future2 = new Future(() => 'task 2');
var future3 = new Future(() => 'task 3');
Future.wait([future1, future2, future3]).then(print).catchError(print);
print('Task added completed'); Result: flutter: task added complete flutter: [task1, the task2, the task3]
Copy the code

Suppose one of them goes wrong

var future1 = new Future(() => 'task 1');
var future2 = new Future(() => throw throw Exception('Task 2 Exception'));
var future3 = new Future(() => 'task 3');
Future.wait([future1, future2, future3]).then(print).catchError(print);
print('Task added completed'); Result: A task was added to flutter. A task was added to flutter2abnormalCopy the code
  • Future.timeout()

Use future.timeout () when network request A throws A timeout exception after 30 seconds

Future.delayed(Duration(seconds: 5), () {
  return 'Network request 5 seconds';
}).timeout(Duration(seconds: 3)).then(print).catchError(print); Result: flutter: TimeoutException after0:00:03.000000: Future not completed
Copy the code

Conclusion:

Normally, the execution of a Future asynchronous task is relatively simple:

When a Future is declared, Dart places the function execution body of the asynchronous task into the Event Queue and immediately returns, with subsequent code continuing to execute synchronously. After the synchronized code is executed, the Event queue will fetch events in the order in which they were added to the event queue (i.e., the declaration order), and then synchronously execute the function body of the Future and subsequent operations.

  1. The microtask queue has the highest priority and will be executed first, so it can hog the event loop as long as there are tasks in the queue.
  2. If there are too many microtask queues, it may cause some blocking of external events such as touch and draw in the event queue
  3. Then () is also added to the microtask queue

Example 1

void main() {
  futureQueueTest();
}

void futureQueueTest() {
  Future future1 = Future(() => null);

  future1.then((value) {
    print('6');
    scheduleMicrotask(() => print(7));
  }).then((value) => print('8'));

  Future future2 = Future(() => print('1'));

  Future(() => print('2'));
  /// Micro tasks
  scheduleMicrotask(() => print('3'));

  future2.then((value) => print('4'));

  print('5');
}
Copy the code
5-- Synchronization 3-- microqueue 6-- first asynchrony 8-- Joined THEN 7-- microtask 1-- next asynchrony 4-- Microtask 2-- next asynchronyCopy the code

Example 2

Future(() => print('f6'))
  .then((_) => Future(() => print('f7')))
  .then((_) => print('f8'));

// Declare an anonymous Future
Future(() => print('f9')); Result: F6 -- asynchronous F9 -- microqueue F7 -- asynchronous F8 -- microtaskCopy the code

After f6 is executed, F7 will be added to the event queue, f9 can be executed in the queue (search for any microtasks that can be executed), f8 will be executed after F7

Example 3

Future(() => print('f1'));
Future(() => print('f2'));

//f3 executes then 3 immediately
Future(() => print('f3')).then((_) => print('then 3'));

// Then 4 is added to the microtask queue and executed as soon as possible
Future(() => null).then((_) => print('then 4'));
print("main thread"); Result Main thread -- synchronous f1 -- asynchronous (search round without microtask) F2 -- asynchronous (search round without microtask) f3 -- asynchronous (add then to microtask) then3-- Microtasks (search a circle without microtasks) then4End -Copy the code

Example 4

Future(() => print('f1'));// Declare an anonymous Future
Future fx = Future(() =>  null);// Declare Future FX with the body null

// Declare an anonymous Future and register two THEN. In the first THEN callback, a microtask is initiated
Future(() => print('f2')).then((_) {
  print('f3');
  scheduleMicrotask(() => print('f4'));
}).then((_) => print('f5'));

// Declare an anonymous Future and register two THEN. The first then is a Future
Future(() => print('f6'))
  .then((_) => Future(() => print('f7')))
  .then((_) => print('f8'));

// Declare an anonymous Future
Future(() => print('f9'));

// register a THEN for fx whose execution body is null
fx.then((_) => print('f10'));

// Start a microtask
scheduleMicrotask(() => print('f11'));
print('f12'); Result F12 -- Synchronous (search microtask F11) F11 -- microtask (search microtask - go asynchronous) F1 -- Asynchronous (search microtask no) F10 -- then Go microtask F2 -- go microtask F3 -- Go microtask F5 -- Go microtask F4 -- F9 -- async f7 -- async F8 -- async f9 -- async F7 -- async F8 -- async f9 -- async F7 -- async F8 -- async microtaskCopy the code

Example 5

Future(() => print('f1'))
  .then((_) async= >await Future(() => print('f2')))
  .then((_) => print('f3'));
Future(() => print('f4')); Result: F1 -- asynchronous F4 -- Asynchronous F2 -- Asynchronous F4 -- microtaskCopy the code

Stream

A Stream is an asynchronous sequence of events. This is like an asynchronous iterator. Instead of getting the value immediately, the iterated value is obtained when the Stream is ready for the event.

  1. The synchronous stream immediately sends an event to StreamSubscription, the stream’s listener, when the add, addError, or close methods are executed.
  2. Asynchronous flows always send events after the code in the event queue completes execution.
Create a Stream

The dart. Academy/streams – and… Wall –

Simply put, a flow is a source of asynchronous events that are passed sequentially. Some data events are sometimes referred to as elements of streams because of their similarity to lists; Some error events are notification of failure. Once all the data elements have been emitted, a special event notification flow has been completed, which notifies any listeners that there are no more.

The main advantage of using streams for communication is that it keeps code loosely coupled. The owner of the stream can emit a value when it becomes available, and it doesn’t need to know anything about who is listening or why. Similarly, the consumer of the data only needs to follow the stream interface, and the way the data is generated for the stream is completely hidden.

There are four main classes in Dart’s asynchronous library for managing flows:

  • Stream: This class represents asynchronous data streams. Listeners can subscribe to be notified when new data events arrive.
  • EventSink: A sink is like a stream that flows in the opposite direction. Adding a data event to EventSink imports the data into the connected stream.
  • StreamController: StreamController simplifies flow management, automatically creates streams and sinks, and provides a way to control traffic behavior.
  • StreamSubscription: Listeners on a stream can save a reference to their subscription, which allows them to pause, resume, or unsubscribe a received data stream.

In most cases, you don’t instantiate the first two directly, because when you create the StreamController, you get the stream and receiver directly. The data subscriber listens for updates on the Stream instance, and EventSink is used to add new data to the Stream. Subscribers to a stream can manage their subscriptions using StreamSubscription instances.

Let’s take a look at some basic flow code to get familiar with how to use the various classes. Mastering these patterns will help you create Flutter widgets that need to communicate with external code. They will allow you to facilitate class-to-class communication in a loosely coupled manner.

  • Stream Basics

Here is a basic example of a string data stream using all four classes:

/ / create the stream
final controller = StreamController<String> ();// Subscribe to listen
final subscription = controller.stream.listen((String data) {
  print(data);
});

/ / add
controller.sink.add("Data!");
Copy the code

With the StreamController instance, you can access the stream to listen and respond to data events using the stream instance’s LISTEN () method, or you can access the sink to add new data events to the stream using EventSink’s Add () method. The Stream’s Listen () method returns an instance of StreamSubscription, which you can use to manage subscriptions to streams.

It should be noted that the controller has an add() method that handles any data forwarded to the receiver:

controller.add("Data!");
Copy the code

You do not need to use the sink reference directly to add data to the stream.

If an error occurs and your stream listener needs to be notified, you can use addError() instead of add():

controller.addError("Error!");
Copy the code

As with Add (), errors are sent over the stream through the sink.

  • Use of the Stream

Typically, controllers and their receivers are private to data producers, while streams are public to one or more consumers. If you have a class that needs to communicate with code outside of itself, perhaps a data service class, you can use the following pattern:

import 'dart:async';

class MyDataService {
  final _onNewData = StreamController<String> (); Stream<String> get onNewData => _onNewData.stream;
}
Copy the code

You need to import the Dart: Async library to get access to the StreamController. The private _onNewData variable represents StreamController and is used to provide incoming data to any user of the service, and we use generics to specify that all data is in string form. The name of the controller variable deliberately matches the public getter onNewData to make it clear which controller belongs to which flow. The getter returns a Stream instance of the controller through which the listener can provide a callback to receive data updates.

Listen for new data events:

final service = MyDataService();

service.onNewData.listen((String data) {
  print(data);
});
Copy the code

Once a reference to the data service is created, a callback can be registered to receive data added to the flow.

You can choose to provide a callback for errors and be notified when the controller closes the stream:

service.onNewData.listen((String data) {
  print(data);
},
onError: (error) {
  print(error);
},
onDone: () {
  print("Stream closed!");
});
Copy the code

Here, we include anonymous callback functions for the onError and onDone arguments to the stream’s Listen () method.

In this example, we create a flow that holds only a single listener. What if you need more?

  • Multi-user flow

Sometimes, the data for the stream is for a single receiver, but in other cases, you may want to allow any number of receivers. For example, different parts of the application may depend on updates from a single data source, including user interface elements or other logical components. If you want to allow multiple listeners on your stream, you need to create a broadcast stream:

class MyDataService {
  final _onNewData = StreamController<String>.broadcast();
  Stream<String> get onNewData => _onNewData.stream;
}
Copy the code

Using the broadcast() constructor named StreamController provides a multi-user stream. In this way, any number of listeners can register callbacks to notify new elements on the flow.

Next, we’ll see what to do when the stream is no longer needed.

  • Close the stream

If the data provider has no more data to provide, you can use the controller to close the flow. All registered onDone callbacks will be called:

class MyDataService {
  final _onNewData = StreamController<String>.broadcast();
  Stream<String> get onNewData => _onNewData.stream;
  
  voiddispose() { _onNewData.close(); }}var _streamController = StreamController<int>.broadcast();
_streamController.stream.listen((event) => print("$event"));
_streamController.stream.listen((event) => print("$event"));
_streamController.sink.add(100);
_streamController.close();
Copy the code

This version of the data service class includes a Dispose () method that can be used to solve some problems. In its body, the controller’s close() method destroys the flow associated with it. Streams should always be closed when they are no longer needed. If the data service instance is dropped and scheduled for garbage collection without closing its stream, you may get a memory leak in your application.

Consumers of streams may also need to manage the data stream, which is where subscriptions come in.

  • Managing stream subscriptions

Listeners that save a reference to a streaming subscription can suspend, resume, or permanently cancel the subscription. Suspended subscriptions do not generate more stream data until they are resumed, although data events are buffered before resuming, and they are all delivered if the stream is resumed.

Pause and then resume stream subscriptions:

final service = MyDataService();

final subscription = service.onNewData.listen((String data) {
  print(data);
});

subscription.pause();

/// restore
subscription.resume();
Copy the code

Obviously, you wouldn’t normally pause and resume the subscription immediately, but the code snippet can illustrate the correct method call.

If the listener no longer needs the data in the stream subscription, it can unsubscribe:

subscription.cancel();
Copy the code

After unsubscribing, a new listener callback can be registered at any time, but a new subscription instance will be generated. Once a subscription is canceled, it cannot be reused.

Next, we’ll look at another way to provide data to the stream.

  • Asynchronous generators

We’ve seen how Dart’s async keyword can be added to a function that returns a single value via Future asynchrony. It turns out that there is a version of this concept for streams in the form of the async* keyword. Mark the function async* to convert it into a data generator function that can asynchronously return a series of values. This is a good mode to use in Flutter’s most popular BLoC implementation, Flutter_bloc, for managing Flutter application state.

Let’s look at a simple example:

Stream<int> count(int countTo) async* {
  for (int i = 1; i <= countTo; i++) {
    yieldi; }}// place this code in a function somewhere
count(10).listen((int value) {
  print(value); }); Results:1
2
3
4.9
10
Copy the code

This code will output values 1 through 10. The async* keyword makes count() an asynchronous generator function. When count() is called, a Stream is immediately returned, which is why we can call listen() directly. The stream’s Listen () method requires a callback function, in which we print each value that arrives.

The generator function uses the yield keyword to inject values into the stream one at a time. Essentially, yield calls the Add () method of the StreamController instance for you. You could generate such generator functions manually without using special keywords, but this would involve using patterns discussed earlier, such as creating your own StreamController, which would be more verbose and require you to track everything more explicitly.

It is important to understand that the main advantage of an asynchronous generator function is its asynchronous nature, which was not obvious in the previous example. Let’s add a small change to make things clearer:

Stream<int> count(int countTo) async* {
  for (int i = 1; i <= countTo; i++) {
    yield i;
    await Future.delayed(const Duration(seconds: 1));
  }
}

count(10).listen((int value) {
  print(value); }); The result is the same as above: the difference is that each print is delayed by a secondCopy the code

If we add a one-second delay between each yield statement, values will be added to the stream every second, rather than almost immediately. When this code is executed, values from 1 to 10 appear in the debug console in an interlaced fashion, rather than all at once. Generator functions are free to spend as much time as they need to generate values, generating each value only when it is ready.

Example:

Future<int> sumStream(Stream<int> stream) async {
  var sum = 0;
  await for (var value in stream) {
    sum += value;
  }
  return sum;
}

Stream<int> countStream(int to) async* {
  for (int i = 1; i <= to; i++) {
    yieldi; }}var stream = countStream(10);
var sum = await sumStream(stream);
print(sum); / / 55
Copy the code

EventBus

Event buses are commonly used for state sharing between components, but there are specialized packages for state sharing between components, such as Redux, and the Provider described earlier. For some simple applications, the event bus is sufficient to meet business needs. If you decide to use a state management package, be sure to decide whether it is really necessary for your APP to use it to prevent “simplifying to complexity” and over-design

Realize the EventBus

// The subscriber calls back the signature
typedef void EventCallback(arg);

class EventBus {
  // Private constructor
  EventBus._internal();

  // Save the singleton
  static EventBus _singleton = new EventBus._internal();

  // Factory constructor
  factory EventBus()=> _singleton;

  // Save event subscriber queue, key: event name (id), value: subscriber queue corresponding to the event
  var _emap = new Map<Object.List<EventCallback>>();

  // Add subscribers
  void on(eventName, EventCallback f) {
    if (eventName == null || f == null) return; _emap[eventName] ?? =new List<EventCallback>();
    _emap[eventName].add(f);
  }

  // Remove the subscriber
  void off(eventName, [EventCallback f]) {
    var list = _emap[eventName];
    if (eventName == null || list == null) return;
    if (f == null) {
      _emap[eventName] = null;
    } else{ list.remove(f); }}// Trigger an event, after which all subscribers of the event will be called
  void emit(eventName, [arg]) {
    var list = _emap[eventName];
    if (list == null) return;
    int len = list.length - 1;
    // Reverse traversal to prevent subscribers from removing self-inflicted subscript mislocations in callbacks
    for (var i = len; i > - 1; --i) { list[i](arg); }}}// Define a top-level (global) variable that can be used directly by the page
var bus = new EventBus();
Copy the code

use

// in page A.// Listen for login events
bus.on("login", (arg) {
  // do something
});

// Login page B.// After successful login, the login event is triggered, and the subscriber on page A will be invoked
bus.emit("login", userInfo);
Copy the code

Or use the library pub.dev/packages/ev…

Notification (as opposed to provider to pass data)

The Notification function is to send a Notification to report the status change of child nodes. Changes to Notification data bubble up through the Widgte tree, and we tend to send notifications at the lower level of Wdiget and process them at the upper level

Notification has a dispatch(Context) method, which is used to distribute notifications. As we said, context is actually an interface that operates on Element. It corresponds to nodes in the Element tree. Notifications bubble up from the Element node corresponding to the context.

// Create a notification class
class MyNotification extends Notification {
  final String info;
  MyNotification(this.info);
}

// Create child widgets to send notifications
class ChildWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: RaisedButton(
        /// Dispatch (Context)---- Sends notifications
        onPressed: () => MyNotification("I am randomly sending the following data:${Random().nextInt(1000)}").dispatch(context),
        child: Text("Submit"),),); }}//parentWidgte uses NotificationListener to receive notifications and display them
class ParentWidget extends StatefulWidget {
  @override
  _ParentWidgetState createState() => _ParentWidgetState();
}

class _ParentWidgetState extends State<ParentWidget> {
  String msg = "";

  onReceiveMessage(String message) {
    setState(() {
      msg =message;
    });
  }

  @override
  Widget build(BuildContext context) {
    // Listen ---- MyNotification(scroll down)
    return NotificationListener<MyNotification>(
      child: Column(
        children: [Text(msg), ChildWidget()],
      ),
      onNotification: (notification) {
        // Receive data
        onReceiveMessage(notification.info);
        return true; }); }}Copy the code

Scroll to monitor

NotificationListener<ScrollEndNotification>(
      onNotification: (notification){
        print(notification.metrics.pixels); // The current scroll position
        print(notification.metrics.maxScrollExtent); // Maximum range of roll work
        switch (notification.runtimeType){
          case ScrollStartNotification: print("Start rolling"); break;
          case ScrollUpdateNotification: print("Rolling"); break;
          case ScrollEndNotification: print("Roll stop"); break;
          case OverscrollNotification: print("Scroll to the boundary"); break;
        }
        return true;
      },
      child: ListView.builder(
          itemCount: 50,
          itemBuilder: (context, index) {
            return ListTile(title: Text("$index")); }),)Copy the code

Provider Status Management

Github.com/rrousselGit…

Blog.csdn.net/lpfasd123/a…

Github.com/sunyongjian… [React]

  1. Why do we need state management

If the application of Flutter is simple enough, as a declarative framework, you might just need to map data to views. You probably don’t need state management, as follows. [Local state]– So know what variables to put into app-level state.

But as you add functionality, your application will have dozens or even hundreds of states. This is what your application should look like.

It’s so complicated! Also, multiple pages share the same state and need to synchronize the state.

Flutter provides a mode of state management called a StatefulWidget. While you can use callback when State belongs to a particular Widget and communicating between widgets, when nesting is deep enough, we add an awful lot of junk code. [Local state]– So know what variables to put into app-level state.

Basic use of providers:

  • Define data model management variables

  • Combine states with components that need to use states through, for example, the ChangeNotifierProvider

  • Use provider. of or Consumer to monitor or change the state

    Consumer<Counter>(
      builder: (BuildContext context, Counter value, Widget child) {
        return Text('${value.count}');
      }
    /// or
    final counter = Provider.of<Counter>(context);
    Copy the code

    Multiple providers writing a single Provider depends on how many providers flutter provides

    MultiProvider(providers: [
      ChangeNotifierProvider<AppProvider>(create: (_) => AppProvider()),
      ChangeNotifierProvider<LocaleProvider>(create: (_) => LocaleProvider()),
    ], child: MyApp())
    
    /// Nesting is the same as above
    ChangeNotifierProvider(
      create: (context) => AppProvider(),
      child: ChangeNotifierProvider<LocaleProvider>(
        create: (context) => LocaleProvider(),
        child: MyApp(),
      ),
    ))
    
    ChangeNotifierProvider.value(
        value: AppProvider(),
        child: MyApp()
      )
    
    /// . Etc.
    Copy the code

www.jianshu.com/p/9334b8f68…

  1. Status Management Provider

Provider is easy to understand from the name, it is used to provide data, whether on a single page or in the entire app has its own solution, we can easily manage state. Arguably, the goal of a Provider is to completely replace StatefulWidget.

This is because the Provider can only provide constant data and cannot tell dependent child widgets to refresh. If you want to use a Provider that causes change, use the following Provider

  • ListenableProvider: A special provider that uses the Listenable object. The ListenableProvider listens on the object and requires widget refactoring whenever the listener is called
  • ChangeNotifierProvider: Inherits ListenableProvider, updates components, and does so automaticallyChangeNotifier.disposeThis is also different from ListenableProvider
  • ValueListenableProvider: it supports only a single data to monitor, there are two ways, one kind of ValueListenableProvider. Value (), another ValueListenableProvider (), two ways are almost the same.

These two providers are related to asynchronous execution

  • StreamProvider: listen to ValueListenable and only expose ValuelistEnable.value
  • FutureProvider: Accepts the Future value and updates the dependency when the Future is complete.

  • The Provider can only fetch values, not update them

The model class inherits from ChangeNotifier to give the model class the ability to manage listeners:

class Counter with ChangeNotifier {
  int _count = 0;

  int get count => _count;

  voidincrement() { _count++; notifyListeners(); }}Copy the code

Codingdict.com/questions/9… The simplest way to use the ChangeNotifier internal method notifyListeners cannot extend model classes that are already extending another class. ChangeNotifier because Dart does not allow multiple inheritance. In this case, you have to use mixins, so it makes sense to use with in this case.

ChangeNotifier inherits the Listenable class and implements addListener, removeListener, and other operations. This is a typical observer mode. It can automatically manage listeners and notify them.

In the above code, the model class Counter exposes a private _count and the count method that gets the private data. We can use this data when building the Widget, and we can call increment to update this data. The Widget is notified of the refresh status by notifyListeners

Provider

Provider is the most basic Provider widget type in the Provider package. It can provide a value to all of the included widgets, but does not update the widget when the value changes.

class AppProvider {
    
    AppProvider({this.counter=0});

    int counter = 0;

    Future<void> incrementCounter() async {
      await Future.delayed(Duration(seconds: 2));
      counter++;
      print(counter); }}Copy the code

The constructor

Provider({
    Key? key,
    required Create<T> create,
    Dispose<T>? dispose,
    bool? lazy,
    TransitionBuilder? builder,
    Widget? child,
  })
Copy the code
  • Create: Returns the data Model to be shared
  • Builder: The Builder pattern is essentially a syntax sugar that provides a context parameter that can be used to generate widgets.
> < span style = "max-width: 100%; clear: both; min-height: 1em; If you want to instantiate your notification program, you should use the create, but if you notice procedures have been instantiated, you don't need to use the create, as long as you use ChangeNotifierProvider. The value 2. Builder: If you have some criteria to display a particular widget, for example, if in your logic you need to choose between two widgets, you will use the generator and pass its child, which is the static widget dart ChangeNotifierProvider(create: (context) => ApplicationState(), builder: (context, child){ if(condition){ return SomeWidget(); } // Use the original child widget return child; }, child:App(), ) ```Copy the code
  • Dispose: Releases resources in a callback
```dart @override void dispose() { stream.dispose(); super.dispose(); } ` ` `Copy the code
  • Lazy: When a Provider is created, the data is not actually created, i.e. the Create method in the constructor, but is created when the data is first accessed. (Lazy = true can be used to delay loading when the initialization logic of the data is complex.)

Provider.value(): When the value is simple and does not need to be related to its life cycle, we can use the named function provider.value to make it easier to create a Provider.

 / / simplified version
 ChangeNotifierProvider.value(value: ProSpanOneProvider()),
   
 // The effect is the same as above
 ChangeNotifierProvider(create: (context) => ProSpanOneProvider())
Copy the code
 MultiProvider(providers: [
      Provider<User>.value(value: new User('Flutter'.300)),
      Provider<int>.value(value: 200),
      Provider<bool>.value(value: false)
    ], child: MyHomePage(title: 'Peovider Demo')))
Copy the code
Text(
'FirstPage Provider: ${Provider.of<String>(context)} | '
'${Provider.of<int>(context)} | ${Provider.of<bool>(context)} | ${Provider.of<User>(context).name = 'Hello World! '}',
style: TextStyle(color: Colors.redAccent)),
Copy the code

The Provider of < T > () (when can’t find the corresponding value, will quote ProviderNotFoundException exception)

Second argument: no need to listen for changes (listen: false does not call build again)

Provider.of<CounterNotifier>(context, listen: false).increment();
Copy the code

The only static method exposed by a Provider is to look up the most recent value of type T that is embedded with the Provider. Use the InheritedWidget as an example.

ListenableProvider 和 ChangeNotifierProvider

ListenableProvider: Binds data through the constructor and listens. Dispose should be destroyed when it is removed from the Widget Tree. Note: the constructor create cannot be empty;

ChangeNotifierProvider: Different from ListenableProvider, dispose the Widget automatically when it is deleted from the Tree. Note: the constructor create cannot be empty;

ListenableProvider({
  Key? key,
  required Create<T> create,
  Dispose<T>? dispose,
  bool? lazy,
  TransitionBuilder? builder,
  Widget? child,
})

ChangeNotifierProvider({
  Key? key,
  required Create<T> create,
  bool? lazy,
  TransitionBuilder? builder,
  Widget? child,
}) 
Copy the code

It’s not very different from Provider usage

StreamProvider 和 FutureProvider

StreamProvider provides streams, and FutureProvider requires a Future

  StreamProvider({
    Key? key,
    requiredCreate<Stream<T>? > create,required T initialData,
    ErrorBuilder<T>? catchError,
    UpdateShouldNotify<T>? updateShouldNotify,
    bool? lazy,
    TransitionBuilder? builder,
    Widget? child,
  })


  FutureProvider({
    Key? key,
    requiredCreate<Future<T>? > create,required T initialData,
    ErrorBuilder<T>? catchError,
    UpdateShouldNotify<T>? updateShouldNotify,
    bool? lazy,
    TransitionBuilder? builder,
    Widget? child,
  })
Copy the code

The FutureProvider carries a Future and updates the dependency when the Future is complete

Streamproviders are specifically used to provide a Single Stream.

  • InitialData: You can declare the initial value of this stream through this property
  • CatchError: This property is used to catch errors in the stream. After the stream is addError, you’ll be able to handle the exception data with the T Function(BuildContext Context, Object Error) callback. It is very useful in practical development.
  • UpdateShouldNotify: Used to tell the InheritedWidget whether notification must be passed to all child widgets (registered/subscribed) if changes are made to data
@override bool updateShouldNotify(ShareInherited oldWidget) { 
  // If true is returned, the widget is dependent on the subtree (called in the build function)
  / / the child widgets ` state. DidChangeDependencies ` will be invoked
  boolresult = oldWidget.data ! =this.data; 
  print('ShareInherited updateShouldNotify result = $result'); 
  return result; }
Copy the code

FutureProviderThe use of

FutureProvider This Provider provides asynchronous data to widgets. Future is an asynchronous development pattern that is available in concurrent libraries for most modern software languages. In short, the Future will schedule some tasks, then release the current thread until the original task is completed, and then parse a value. This concept is similar to Observables and Promises. Instead of saying, “This is a value of type T,” say, “Finally, there will be a value of type T.”

In this example, we will read a list of User objects from a file and display them in a color-coded scrollable ListView

. The asynchronous part of the process is opening and reading files. Typically, reading a file synchronously is an expensive operation, but when it is done asynchronously, the rest of the application can continue running, and the widget will be updated when the file reading is complete.

First, we need to create a UserProvider class that implements the logic to read the file and deserialize the data to the User object. To learn more about using FLUTTER JSON data, check out our guide to serializing and deserializing JSON in Flutter.

class User {
  final String name;
  final String email;

  User(this.name, this.email);

  User.fromJson(Map<String.dynamic> json)
      : name = json['name'],
        email = json['email'];

  Map<String.dynamic> toJson() =>
    {
      'name': name,
      'email': email,
    };
}

class UserProvider {
  final String _dataPath = "assets/data/users.json";
  List<User> users;

  Future<List<User>> loadUserData( ) async {
    var dataString = await loadAsset();
    Map<String.dynamic> jsonUserData = jsonDecode(dataString);
    users = UserList.fromJson(jsonUserData['users']).users;
    print('done loading user! ' + jsonEncode(users));
    return users;
  }

  Future<String> loadAsset() async {
    /// Future.delayed(Duration(seconds: 3), () {}) three seconds
    return await Future.delayed(Duration(seconds: 10), () async {
      return awaitrootBundle.loadString(_dataPath); }); }}Copy the code

To illustrate the delay of loading larger files, we artificially added a 10-second delay before attempting to read the file. In the meantime, we’re going to show a spinner.

Next, we add the FutureProvider to the MultiProvider we use with the ChangeNotifierProvider. In this case, we want to inject the Future into the FutureProvider. Since our constructor does not return the future, we need to call a method from FutureProvider to return the future; LoadUserData ().

MultiProvider(
  providers: [
    ChangeNotifierProvider(builder: (_) => DataProvider()),
    FutureProvider(
      create: (_) => UserProvider().loadUserData()
      initialData:[]
    )
  ],
  child:runApp()
)
Copy the code
class MyUserPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
  /// To get the data
  var _users = Provider.of<List<User>>(context);
    return Column(
      children: <Widget>[
        Padding(
          padding: EdgeInsets.all(10.0),
          child: Text(
              'FutureProvider Example, users loaded from a File'
          ),
        ),
        Expanded(
          /// judge_users
          child: _users == null ? Container(child: CupertinoActivityIndicator(radius: 50.0)) :
          ListView.builder(
              itemCount: _users.length,
              itemBuilder: (context, index){
                return Container(
                    height: 50,
                    color: Colors.grey[(index*200) % 400],
                    child: Center(
                        child: Text(
                            '${_users[index].firstName} ${_users[index].lastName} | ${_users[index].website}'))); }))],); }}Copy the code

StreamProvideruse

StreamProvider The purpose of this Provider is for widgets to allow access to the state that occurs in part of the stream. Streams are asynchronous, but unlike Future

, streams are not parsed after returning a value. Instead, the stream can continue to supply values until the stream is closed.

In our example, we are creating a flow that generates one Event per second. Every second, we add a text widget using the current value _count in the EventProvider. Note: Streams do not need to set intervals.

Let’s take a look at our EventProvider. This class is very simple.

class EventProvider {
  Stream<int> intStream() {
    Duration interval = Duration(seconds: 2);
    int _count = 0;
    // Create a flow using periodic. The first argument is the interval time and the second argument is the callback function
    return Stream<int>.periodic(interval, (int_count) => _count++); }}Copy the code

Now, let’s add the EventProvider to the MultiProvider in the same way we configured the FutureProvider. When we register the provider, we call the intStream() method because it returns a Stream and the StreamProvider requires that the value provided be a valid Stream.

  MultiProvider(
      providers: [
        ChangeNotifierProvider(builder: (_) => DataProvider()),
        StreamProvider(create: (_) => EventProvider().intStream(), initialData: 0)
      ],
      child:runApp()
  )
Copy the code

Let’s see how this works in the MyEventPage widget. As in our first example, using ChangeNotifierProvider, we use provider.of

(context) to get the latest instance of T in the current context; In this case, T is an int, not a stream.

 class MyEventPage extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
      var _value = Provider.of<int>(context);
      return Container(
        child: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Text('StreamProvider Example'),
              SizedBox(height: 150),
              Text('${_value.toString()}', style: Theme.of(context).textTheme.display1 ) ], ) ) ); }}Copy the code

Since the StreamProvider provides an int stream, we can simply listen for the latest int state change and then update our Text widget.

ValueListenableProvider

ValueListenableProvider is used to provide models that implement inheritance/mixin/ValueListenable. It is actually dedicated to a ChangeNotifier that only has a single change of data

final _isOKNotifier = ValueNotifier(false);

_isOKNotifier.value = true;
Copy the code

ProxyProvider

ProxyProvider itself is a Provider. It combines the data of multiple other providers into a new object and sends the result to one Provider after another. When any data of these combined providers is updated, the new object will be updated.

Use this type of provider when there is a dependency between two View Models. If you want to provide two models, but one model depends on the other, you can use a ProxyProvider. A ProxyProvider takes A value from one Provider and injects it into another Provider

  1. ProxyProvider0 (the base class)
  2. ProxyProvider2 (pass values from 2 providers into the proxy)
  3. ProxyProvider3
ProxyProvider3<A, B, C, Result>( builder: (_, a, b, c, previous) { ... })Copy the code
  1. ProxyProvider4
  2. ProxyProvider5
  3. ProxyProvider6
ProxyProvider6<A, B, C, D, E, F, Result>(
  builder: (context, a, b, c, d, e, f previous) {
    finalg = Provider.of<G>(context); . })Copy the code
  1. ProxyProvider
ProxyProvider<A, Result>(
  builder: (context, a, previous) {
    final b = Provider.of<B>(context);
    finalc = Provider.of<C>(context); . },)Copy the code
  1. ChangeNotifierProxyProvider
  2. ListenableProxyProvide

ProxyProvider won’t listen to any change and ChangeNtofierProxyProvider can

// Image model
class PicModel with ChangeNotifier {
  int counter = 0;

 // The Future's asynchronous delay is two seconds plus one to emulate and then notify updates -- plus print
  Future<void> upLoadPic() async {
    await Future.delayed(Duration(seconds: 2));
    counter++;
    notifyListeners();
    print(counter); }}// The submitted model
class SubmitModel {
  PicModel _model;
  /// I'm going to pass in a new PicModel() and upload it
  SubmitModel(this._model);

  Future<void> subMit() async {
    await_model.upLoadPic(); }}class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        // get count here
        ChangeNotifierProvider<PicModel>(create: (context) => PicModel()),
        // Host dependency
        ProxyProvider<PicModel, SubmitModel>(
          update: (context, myModel, anotherModel) => SubmitModel(myModel),
        ),
      ],
      child: Scaffold(
        appBar: AppBar(
          title: Text('provider'),
        ),
        body: Column(
          children: <Widget>[
            Builder(
              builder: (context) {
                PicModel modol = Provider.of<PicModel>(context);
                return Container(
                    margin: const EdgeInsets.only(top: 20),
                    width: MediaQuery.of(context).size.width,
                    padding: const EdgeInsets.all(20),
                    alignment: Alignment.center,
                    color: Colors.lightBlueAccent,
                    // Here is the number of submissions
                    child: Text('Submit picture:${modol.counter}')); },),/ / consumption
            Consumer<PicModel>(
              builder: (context, model, child) {
                return FlatButton(
                    color: Colors.tealAccent,
                    // The number of clicks to upload +1
                    onPressed: model.upLoadPic,
                    child: Text("Submit picture"));
              },
            ),
            Consumer<SubmitModel>(
              / / consumption
              builder: (context, model, child) {
                return FlatButton(
                    color: Colors.tealAccent,
                    // The number of clicks to upload +1
                    onPressed: model.subMit,
                    child: Text("Submit")); },),],),),); }}Copy the code

Through ProxyProvider, we directly call the value of provider. of(context), and the Provider associated with the User is not registered, so it can run effectively.

Get method and set method

The get and set methods are a special pair of methods used to read and write properties of objects. In fact, there is an implicit GET method for each property of an instance object, and a set method for non-final properties

  • Define and use get and set methods
class Rectangle {
  double left, top, width, height;

  Rectangle(this.left, this.top, this.width, this.height);

  // Define two attributes generated by the calculation: right and bottom.
  double get right => left + width;
  set right(double value) => left = value - width;
  double get bottom {
    return top + height;
  }

  set bottom(double value) => top = value - height;
}

void main() {
  var rect = Rectangle(3.4.20.15);
  assert(rect.left == 3);
  rect.right = 12;
  assert(rect.left == - 8 -);
}
Copy the code

The right and bottom get and set methods are defined to perform some computation before assignment or value, extending direct manipulation of attributes.

If we need to change the meaning of right and bottom or the calculation method later, we only need to change the set and get methods, and the external references do not need to be changed at all, which also makes the code more extensible

  • Get the results using the GET method
String get progress{
  if(completed <= 0) {return "0%";
  }
  double pro = 100 * completed / total;
  return pro.toStringAsFixed(5).toString() + "%";
}
Copy the code

{} is used to wrap the function body

The life cycle

StatelessWidget and StatefulWidget. The difference between StatelessWidget and StatefulWidget is that new instances must be created when the StatelessWidget component changes. The StatefulWidget component can directly change the state of the current component without creating a new instance

  • StatelessWidget: is a stateless component that does not change once the page is generated, so there is only a createElement and build life cycle
  • StatefulWidget: Stateful component, which can be refreshed in the page. Therefore, the state of this component changes more, including initialization stage, update stage and destruction stage

StatefulWidget cycle

  • createState

    CreateState is executed immediately when StatefulWidget is invoked

    CreateState () is used to create the state associated with the StatefulWidget, which may be called several times during the life of the StatefulWidget. For example, when a StatefulWidget is inserted into multiple locations in the widget tree at the same time, the Flutter Framework calls this method to generate a separate instance of State for each location. When a component is removed from the widget tree and then inserted back into the widget tree, CreateState will be called to create a new State

    CreateState () is called when a new StatefulWidget is built, and a subclass of StatefulWidget must override this method

    class MyScreen extends StatefulWidget {
      @override
      _MyScreenState createState() => _MyScreenState();
    }
    Copy the code

    When the createState function completes, the current component is already in the component tree. Mounted is set to true by the Framework. The value of Mounted after the widget. buildContext is set to true. Mounted is true until the Dispose method is called.]

    • State

      A StatefulWidget class corresponds to a State class. State represents the State to be maintained by its corresponding StatefulWidget. State information stored in State can be:

      • It can be read synchronously when a widget is built

      • The State can be changed during the widget lifecycle. When the State is changed, you can manually call its setState() method to notify the Flutter framework of the State change. The Flutter framework receives the message, Its build method is re-called to rebuild the Widget tree to update the UI

  • initState

    You usually need to override this method. Color {red}{this function will only be called once} This function will only be called once. When overriding this method, be sure to call super.initState().

    Native methods:

    1. onCreate()[Android cycle method]
    2. viewDidLoad()[Ios cycle method]

    Usage:

    1. In this method, you can check some properties related to the Widget
    2. Is the mounted state processed?
    3. Initial assignment of State variables can be performed during this period,
    4. You can also interact with the server during this period and set the State by calling setState after retrieving the server data
    @override
    void initState() {
      super.initState();
      // Initialize...
    }
    Copy the code

    Function cannot call BuildContext. DependOnInheritedWidgetOfExactType [see below this you will know that this is what the]

  • didChangeDependencies

    DidChangeDependencies () is called immediately after initState() is first called when a widget is built.

    Called when the State object’s dependency changes to whether the child widget uses data from the InheritedWidget in the parent widget. To explain: Here’s an important concept that InheritedWidget clicks to

    InheritedWidget components are functional components that share data down the tree, that is, InheritedWidget components can get data from the parent component, Through BuildContext dependOnInheritedWidgetOfExactType access. Use inheritedWidgets to share the Theme and Locale information in the Flutter SDK.

    Inheritedwidgets are similar to context in React in that they enable components to transfer data across hierarchies rather than hierarchically. InheritedWidget’s data is delivered from top to bottom in the widget tree, which is the opposite of the direction of Notification delivery.

    Simple example

    class ShareDataWidget extends InheritedWidget {
    ShareDataWidget({
      @required this.data,
      Widget child
    }) :super(child: child);
      
    final int data; // The number of clicks that need to be shared in the subtree
      
    // Define a convenient method for widgets in a subtree to get shared data
    static ShareDataWidget of(BuildContext context) {
      return context.dependOnInheritedWidgetOfExactType<ShareDataWidget>();
    }
    
    // This callback determines whether to notify data-dependent widgets in the subtree when data changes
    @override
    bool updateShouldNotify(ShareDataWidget old) {
        // If true is returned, the widget is dependent on the subtree (called in the build function)
        / / the child widgets ` state. DidChangeDependencies ` will be invoked
        return old.data != data;
      }
    }
    Copy the code
    class _TestWidget extends StatefulWidget {
      @override
        __TestWidgetState createState() => new __TestWidgetState();
      }
    
      class __TestWidgetState extends State<_TestWidget> {
        @override
        Widget build(BuildContext context) {
          // Use shared data in the InheritedWidget
          returnText(ShareDataWidget.of(context)! .data.toString()); }@override
        void didChangeDependencies() {
          super.didChangeDependencies();
          // The InheritedWidget in the parent or ancestor widget is called when the InheritedWidget changes (updateShouldNotify returns true).
          // If there is no dependent InheritedWidget in the build, this callback will not be called.
          print("Dependencies change"); }}Copy the code
    init count = 1;
    
    // Omit some necessary code build....
    ShareDataWidget(
        data: count,
        child: Column(children: [
          _TestWidget(),
          ElevatedButton(
            child: Text('on the'),
            onPressed: () => {
              setState(() => {count++})
            },
          )
        ]),
      )
    Copy the code

    Dependencies Change is printed, and the child component is wrapped around and used by the InheritedWidget’s component

The data, the didChangeDependencies lifecycle is called when the data changes

@override
  void didChangeDependencies() {
    super.didChangeDependencies();
    // The InheritedWidget in the parent or ancestor widget is called when the InheritedWidget changes (updateShouldNotify returns true).
    // If there is no dependent InheritedWidget in the build, this callback will not be called.
  }
Copy the code

Method is the first life cycle to use BuildContext dependOnInheritedWidgetOfExactType method, this method will seldom be rewritten, because the Framework will be called when the change in the dependent on the build, need to rewrite this approach scenario is: Dependency changes require time-consuming tasks, such as network requests for data

The build method is called immediately after the didChangeDependencies method is called when the component state becomes dirty

  • build

    Create various components in the method and draw them to the screen. The Framework calls this method in a variety of situations

    • After calling the initState method
    • After calling the didUpdateWidget method
    • Upon receipt of the call to setState
    • When the dependency of this State object changes (for example, the InheritedWidget of the dependency changes)
    • After calling deActivate, reinsert the State object to another location in the tree

    Since build will be called multiple times, only widget-related logic can be used in this function to avoid state exceptions caused by executing it multiple times. Only the code that builds the component should be included in this method, and no additional functionality, especially time-consuming tasks, should be included

    • We can say one hereaddPostFrameCallbackAddPostFrameCallback is the StatefulWidge render end callback. It is called only once, and the StatefulWidget will not be called after it needs to refresh the UI. AddPostFrameCallback is used by adding a callback to initState

    The method is what the frame rate is at a time

    @override
    void initState() {
      super.initState();
      / / the first
      SchedulerBinding.instance.addPostFrameCallback((_) => {});
      / / the second
      WidgetsBinding.instance.addPostFrameCallback((_) => {});
      // Same effect
    }
    Copy the code
    WidgetsBinding. Instance. AddPersistentFrameCallback ((_) {print (" real-time Frame drawing callback "); // every frame is called back});Copy the code
  • didUpdateWidget

    This is a less common lifecycle approach. DidUpdateWidget () is called whenever the parent Widget(called only when the parent container component is redrawn) changes and needs to redraw the UI. This method has an oldWidget parameter that can be compared to the current widget to perform some additional business logic.

    @override
      void didUpdateWidget(WidgetLiftCyclePage oldWidget) {
        super.didUpdateWidget(oldWidget);
        print("didUpdateWidget");
      }
    Copy the code
  • deactivate

    When a State object is removed from the render tree, the DeActivate lifecycle is called, which signals that the StatefulWidget is about to be destroyed, but sometimes the State is not destroyed, but reinserted into the render tree

    In this method, the current component is typically compared to the previous component. After the Framework calls this method, it sets the component to the dirty state and then calls the Build method, so there is no need to call the setState method in this method.

    @override
    void deactivate() {
      super.deactivate();
      print("deactivate");
    }
    Copy the code
  • dispose

    Permanently Remove Component is called when the component is destroyed to permanently remove the State object from the tree. Resources are released and destroyed in this method

    @override
    void dispose() {
      super.dispose();
      print("dispose");
    }
    Copy the code

    In this method you need to unlisten on the stream, destroy the animation, and so on. It is the reverse operation of initState

    The difference with deactivate is that deactivate can also be re-inserted into the tree, while Dispose indicates that this State object is never in build. After dispose is called, Mounted is set to false, indicating the end of component life cycle. In this case, setState method will throw an exception.

    Why do you think of the following question? Because this problem occurs daily while developing React, page uninstallation is still in the setState method

    For example, if you go to a page and it’s a little slow to load, you go back to the previous page and trigger the Dispse method to destroy the control. This error is reported when setState is executed just after the network load is complete. Example code for the solution is shown below

    if (mounted){ // Check whether setState is mounted
      setState((){
        / / a little
      });
    }
    Copy the code
  • Several concepts

    • mounted

    Mounted is a property in the State object that specifies whether the current component is in the tree. After State is created and before initState is called, the Framework associates State with BuildContext. When the Framework calls dispose, Mounted is set to false, indicating that the component is no longer in the tree.

    CreateState specifies that the component is already in the component tree. Mounted is set to true by the Framework.

      if(mounted){
        setState(() {
          ...
        });
      }
    Copy the code
    • Dirty or clean

    Dirty Indicates that the component is currently dirty. The build function will be executed at the next frame, and the component is dirty after the setState method is called or the didUpdateWidget method is executed.

    Clean corresponds to dirty. Clean indicates that the component is in the clean state. In the clean state, the component does not execute the build function.

    • setState

    The setState method is often called by developers when the component state becomes dirty and is called when there is data to update.

App life cycle

  • reassemble

In Debug mode, this function is called every time a hot reload occurs, so you can add debug code during the debug phase to check for code problems

@override
void reassemble() {
  // TODO: implement reassemble
  super.reassemble();
  print('reassemble');
}
Copy the code

In native Android and iOS development, sometimes we need to do something in the corresponding App life cycle events, such as the App going from background to foreground, from foreground to background, or after the UI is drawn.

In native development, we can override Activity, ViewController lifecycle callback methods, or register notifications related to the application to listen for the App lifecycle and handle them accordingly. With Flutter, you can use the WidgetsBindingObserver class to fulfill the same requirements.

abstract class WidgetsBindingObserver {
  / / page pop
  Future<bool> didPopRoute() => Future<bool>.value(false);
  A push / / page
  Future<bool> didPushRoute(String route) => Future<bool>.value(false);
  // System window related change callback, such as rotation
  void didChangeMetrics() { }
  // Text scaling factor changes
  void didChangeTextScaleFactor() { }
  // System brightness changes
  void didChangePlatformBrightness() { }
  // Localize language changes
  void didChangeLocales(List<Locale> locale) { }
  //App life cycle changes
  void didChangeAppLifecycleState(AppLifecycleState state) { }
  // Memory warning callback
  void didHaveMemoryPressure() { }
  //Accessibility related feature callback
  void didChangeAccessibilityFeatures() {}
}
Copy the code
class _MyHomePageState extends State<MyHomePage> with WidgetsBindingObserver {
  @override
  void initState() {
    super.initState();
    print('+ initState called');
    WidgetsBinding.instance.addObserver(this);
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    super.didChangeAppLifecycleState(state);
    if (state == AppLifecycleState.paused) {
      // went to Background
    }
    if (state == AppLifecycleState.resumed) {
      // came back to Foreground
    }
    print('The current state is:$state');
  }

  // System window related change callback, such as rotation
  @override
  void didChangeMetrics() {
      super.didChangeMetrics(state);
   }

  @override
  void dispose() {
    super.dispose();
    print(+ dispose called);
    WidgetsBinding.instance.removeObserver(this); }}Copy the code

Dispose Cancel listener

DidChangeAppLifecycleState its common state including resumed, inactive, paused the four.

  • Resumed: Is visible and can respond to user input.
  • Inactive: In inactive state, unable to process user responses.
  • Paused: Invisible and does not respond to user input, but continues the activity in the background.
  • Detached: The application still resides on the Flutter engine, but is detached from any host view. When the application is in this state, the engine runs without a view. When the engine is first initialized, it can be in the attaching a View process or after the View is destroyed due to Navigator Pop.

[Record JS garbage collection mechanism]

Blog.csdn.net/qq_17550381…

www.jianshu.com/p/d23ef1c00…

The use of extends, implements, and with in Flutter

  1. Inheritance (keyword extends)
  2. Mixins (keyword with)
  3. Interface implementation (keyword implements)

These three relationships can exist simultaneously, but in a sequential order: extends -> mixins -> implements

Inheritance (extends)

Inheritance in Flutter is the same as inheritance in Java:

  1. Inheritance in Flutter is A single inheritance -class A extends B,C cannot be written this way
  2. Constructors cannot be inherited
  3. Subclasses override methods of superclasses using @override
  4. Subclasses call methods of the superclass with super

Subclasses in a Flutter can access all variables and methods in their parent class because there is no public and private distinction in a Flutter

class Television {
  void turnOn() {
    _illuminateDisplay();
  }

  void _illuminateDisplay(){
    print('niu')}}class SmartTelevision extends Television {
  @override
  void turnOn() {
    // Experiment available "@override" optional -- highly recommended for code reading
    // Calling turnOn prints niuniu niuniu hammer [super.turnon ();]
    super.turnOn();
    _bootNetworkInterface();
  }

  void _bootNetworkInterface(){
    print('Niuniu a hammer')}}Copy the code

Mixed mixins (with)

In the Flutter:

  1. The mixed objects are classes
  2. You can mix multiple
class Television {
  void turnOn() {
    _illuminateDisplay();
  }

  void _illuminateDisplay(){}
}

class Update{
  void updateApp(){}
}

class Charge{
  void chargeVip(){}
}

class SmartTelevision extends Television with Update.Charge  {

  void turnOn() {
    super.turnOn();
    _bootNetworkInterface();
    updateApp();
    chargeVip();
  }

  void _bootNetworkInterface(){
  }

}
Copy the code

Interface implementation (implements)

A Flutter has no interface, ** but each class in a Flutter is an implicit interface that contains all of the class’s member variables and defined methods. 支那

Example: If you have A class A, and you want class B to have the API of A, but not the implementation of A, you should treat A as an interface, and class B implements class A.

So in Flutter:

  1. The class is the interface
  2. When a class is treated as an interface, the methods in the class are the methods of the interface and need to be re-implemented in a subclass, adding @override to the subclass
  3. When a class is used as an interface, the member variables in the class also need to be reimplemented in the subclass. Precede a member variable with @override
  4. There can be multiple implementation interfaces
class Television {
  void turnOn() {
    _illuminateDisplay();
  }

  void _illuminateDisplay(){
  }
}

class Carton {
  String cartonName = "carton";

  void playCarton(){

  }
}

class Movie{
  void playMovie(){

  }
}

class Update{
  void updateApp(){

  }
}

class Charge{

  void chargeVip(){

  }
}

class SmartTelevision extends Television with Update.Charge implements Carton.Movie {
  @override
  String cartonName="SmartTelevision carton";

  void turnOn() {
    super.turnOn();
    _bootNetworkInterface();
    updateApp();
    chargeVip();
  }

  void _bootNetworkInterface(){
  }

  @override
  void playCarton() {
    // TODO: implement playCarton
  }

  @override
  void playMovie() {
    // TODO: implement playMovie}}Copy the code

late

Suppose you have a property whose value comes from a server or some other method, and you cannot initialize it

// An exception will be generated
String name;
Copy the code

Prompt we must initialize, use the keyword late in this case

late String name;
Copy the code

This attribute must be assigned before being used. The following uses it to throw an exception at runtime:

late String name;

void _incrementCounter() {
  print('name length:${name.length}');
}
Copy the code

Correct usage:

late String name;

void _incrementCounter() {
  name = '123';
  print('name length:${name.length}');
}
Copy the code

Conclusion:

  1. Added 2 operators to air safety? And! , 1 keyword late.

  2. ? : after the type to indicate that the current variable can be null, such as int a and int? B, A can’t be null, but B can.

  3. ! If it is null, an exception will be thrown. This operator is often used when a method argument is of a non-null type (int) and the variable passed to the current method is of a nullable type (int?). If the type does not change, add after the variable! , the current variable is not NUL.

  4. Late: indicates late initialization. It is usually used for lazy loading (such as network requests). Variables declared by late must be initialized before being used. When you declare late like this, you’re delaying initialization. Instance construction is deferred until the field is first accessed, rather than initialized at instance construction time. In other words, it makes fields initialized exactly the same as top-level variables and static fields. This can be useful when initialization expressions are performance-intensive and may not be needed.

BottomNavigationBar

BottomNavigationBar({
    Key? key,
    required this.items,// Set of each item of the page [display item in the bottom navigation bar] >BottomNavigationBarItem
    this.onTap,// The callback argument for clicking a navigation subitem is the subindex of the click item.
    this.currentIndex = 0.// The index of the current display item
    this.elevation,// Control the shadow height, default is 8.0
    this.type,/ / BottomNavigationBarType, fixed, by default set to shifting, uncheck there is no label attribute
    Color? fixedColor,// Select item fill color fixedColor and selectedItemColor cannot coexist
    this.backgroundColor,// the entire BottomNavigationBar background color
    this.iconSize = 24.0.// Icon size -- the Icon component is valid only by default 24
    Color? selectedItemColor,// The text color of the selected item
    this.unselectedItemColor,// The text color of the unselected item
    this.selectedIconTheme, // Select the item Icon theme -- of course you need the Icon component to work
    this.unselectedIconTheme,// The item Icon theme is not selected -- of course you need the Icon component to be valid
    this.selectedFontSize = 14.0.// The default text size of the selected item is 14
    this.unselectedFontSize = 12.0.// The text of the unselected item defaults to 12
    this.selectedLabelStyle,// The label style of the selected item
    this.unselectedLabelStyle,// The label style of the unselected item
    this.showSelectedLabels,// Whether to display the selected labels Default to true
    this.showUnselectedLabels,// Whether to display unselected labels Default to true
    this.mouseCursor,// Over the mouse, Web development can understand
    this.enableFeedback,// Whether detected gestures should provide auditory and/or tactile feedback. On Android, when feedback is enabled, a click produces a click and a long press produces a short vibration.
  })
Copy the code
  • BottomNavigationBarItem again, this is simple

    • required Widget icon:The Widget iconThis is necessary
    • Widget? title: This attribute seems to be deprecatedText('')
    • String? label: New property text
    • Widget? activeIcon: Selected Icon Icon
    • Color? backgroundColor: Background color
    • String? tooltip: Text that appears after a long press. The default is label
  • selectedIconTheme

    BottomNavigationBarItem(
      icon: Icon(Icons.title),
      label: 'title',
    )
    
    selectedIconTheme: IconThemeData(
      color: Colors.black,
      size: 24,
    ),
    unselectedIconTheme: IconThemeData(
      color: Colors.black,
      size: 24,),Copy the code

useconstCreate Widget revenue

Juejin. Cn/post / 697721…

Package of the android

Mr. Is the secret key

keytool -genkey -v -keystore ~/key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias learnAwesome
Copy the code
Key library password :2021621===[can be set to current date]Copy the code

key.properties

storePassword=2021621
keyPassword=2021621
keyAlias=learnAwesome
storeFile=key.jks
Copy the code

www.jianshu.com/p/749baa589…

flutter build apk
Copy the code

The whole projectBuildContext

www.jianshu.com/p/509b77b26…

Buildcontext is the Element object created by the Widget

Provider Status Management

import 'package:flutter/material.dart';

class AppProvider extends ChangeNotifier {
  ///The name of the app
  String appName = "Flutter Learning Collection Project";
}
Copy the code

Import a single provider using ChangeNotifierProvider. Import multiple providers using MultiProvider

main(){
  runApp(ChangeNotifierProvider(
      create: (context) => AppProvider(),
      child: ChangeNotifierProvider<AppProvider>(
        create: (context) => AppProvider(),
        child: MyApp(),
      ),
    ))
}
Copy the code
class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    /// Get global APP
    final provider = Provider.of<AppProvider>(context);
    / /...}}Copy the code

The GridView use

Inherited from BoxScrollView

The GridView(which is also part of the sliding component family and has some of the properties common to sliding components) has five constructors available:

  • GridView: This works for scenarios with a finite number of child elements, because the GridView component renders all of the child elements in children at once
  • Gridview.count: applies to fixed column cases, applies to small numbers; GrdiView SliverGridDelegateWithFixedCrossAxisCount use abbreviations (syntactic sugar), the effect
  • GridView. Builder: This is ideal for long list scenarios because the GridView component dynamically creates and destroys child elements based on whether or not they appear on the screen, reducing memory consumption and rendering more efficiently
  • Gridview.custom: You can configure the arrangement rules for the children and also configure the render loading mode for the children
  • Gridview. extent: Applies to situations where entries have a maximum width limit or where there is a small amount of data. The GridView use SliverGridDelegateWithMaxCrossAxisExtent shorthand (syntactic sugar), exactly the effect.

Blog.csdn.net/ruoshui_t/a…

GridView({
    Key? key,
    Axis scrollDirection = Axis.vertical,/// Scroll direction - Horizontal or vertical (default vertical)
    bool reverse = false./// Reverse reaction - everything is generated in the reverse direction plus scrolling similar to chat
    ScrollController? controller,/// Controller (commonly said instance)
    bool? primary,/// Whether insufficient content can be scrolled (default: true). When false, insufficient content cannot be scrolled
    ScrollPhysics? physics,/ / sliding type [AlwaysScrollableScrollPhysics () a scrollable] [NeverScrollableScrollPhysics () forbidden scroll] [BouncingScrollPhysics () Content is less than one screen can not be rolled back (more than one screen can be rolled back)] [ClampingScrollPhysics()
    bool shrinkWrap = false./ / content (the default is false) when is true only to satisfy their own size, https://www.cps12345.com/flutter/11430/
    EdgeInsetsGeometry? padding,// The distance between the left and the edge The distance between the right and the right
    required this.gridDelegate,// Delegate that controls the layout of subitems in the GridView
    bool addAutomaticKeepAlives = true.// Auto save view cache -- whether to release child controls when closing the screen
    bool addRepaintBoundaries = true.// Whether to avoid list item redrawing
    bool addSemanticIndexes = true.// This property indicates whether child controls are wrapped in IndexedSemantics to provide accessible semantics
    double? cacheExtent, // The number of preloaded child controls
    List<Widget> children = const <Widget>[],// An array of child controls
    int? semanticChildCount,/// Number of child controls
    DragStartBehavior dragStartBehavior = DragStartBehavior.start,// Respond to drag timing
    Clip clipBehavior = Clip.hardEdge,// Clipping beyond the content
    ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,// Slide component keyboard hide mode
    String? restorationId,// Slide the location memory flag
  })
Copy the code
  • SliverGridDelegateWithFixedCrossAxisCount

    The number of columns in each row is fixed

    SliverGridDelegateWithFixedCrossAxisCount({
      @required this.crossAxisCount,// The number of columns, i.e. the number of child elements in a row;
      this.mainAxisSpacing = 0.0.// Void spacing along the main axis;
      this.crossAxisSpacing = 0.0.// Void spacing along the secondary axis;
      this.childAspectRatio = 1.0.// The ratio of width to height of child elements.
    })
    Copy the code

    If the width ratio of child elements is not 1, you must set the childAspectRatio property

  • SliverGridDelegateWithMaxCrossAxisExtent

    Segmentfault.com/a/119000001…

    SliverGridDelegateWithMaxCrossAxisExtent({
      @required this.maxCrossAxisExtent,
      this.mainAxisSpacing = 0.0.this.crossAxisSpacing = 0.0.this.childAspectRatio = 1.0,})Copy the code

    MaxCrossAxisExtent basically tells the GridView component what the maximum possible width of a child element is, and then calculates the appropriate column width (which determines how many columns in a row)

GridView.count

GridView.count(
  // The number of widgets in a row
  crossAxisCount: 2.// Vertical spacing between child widgets
  mainAxisSpacing: 10.// Level the spacing between child widgets
  crossAxisSpacing: 10.// Ratio of width to height of child widgets
  childAspectRatio: 2.0.// List of child widgets
  children: <Widget>[
    Container(color: Colors.cyan, child: Text('GridView child ')),
    Container(color: Colors.cyan, child: Text('GridView child ')),
    Container(color: Colors.cyan, child: Text('GridView child ')),
    Container(color: Colors.cyan, child: Text('GridView child ')),,)Copy the code

GridView.extent

GridView.extent(
  maxCrossAxisExtent: 300.// Vertical spacing between child widgets
  mainAxisSpacing: 10.// Level the spacing between child widgets
  crossAxisSpacing: 10.// List of child widgets
  children: <Widget>[
    Container(color: Colors.cyan, child: Text('GridView child ')),
    Container(color: Colors.cyan, child: Text('GridView child ')),
    Container(color: Colors.cyan, child: Text('GridView child ')),
    Container(color: Colors.cyan, child: Text('GridView child ')),,)Copy the code

GridView

Gridview.extent has the same effect as the gridView.extent example above -[true syntax sugar]

GridView(
  gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
      maxCrossAxisExtent: 100,
      crossAxisSpacing: 10,
      mainAxisSpacing: 10),
  // List of child widgets
  children: <Widget>[
    Container(color: Colors.cyan, child: Text('GridView child ')),
    Container(color: Colors.cyan, child: Text('GridView child ')),
    Container(color: Colors.cyan, child: Text('GridView child ')),
    Container(color: Colors.cyan, child: Text('GridView child ')),,)Copy the code

GridView.builder

GridView.builder(
  reverse: true.// shrinkWrap: true,
  padding: EdgeInsets.all(20),
  physics: ClampingScrollPhysics(),
  itemCount: 100,
  itemBuilder: (BuildContext context, int index) {
    return Container(color: Colors.cyan, child: Text('22222'));
  },
  gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
      crossAxisCount: 2, childAspectRatio: 0.76, crossAxisSpacing: 10),Copy the code

Flutter anomaly capture

www.jianshu.com/p/b50e2dac5…

Flutter. Cn/docs/testin…

zhuanlan.zhihu.com/p/86515127

Segmentfault.com/a/119000002…

The onError parameter of runZoned is Deprecated and will be replaced with runZonedGuarded

main(){
  /// Custom error reporting page
  runZonedGuarded(() {
    /// Handling errorsErrorWidget.builder = (FlutterErrorDetails details) { Zone.current.handleUncaughtError(details.exception, details.stack!) ;/// Customize an ErrorWidget to show build errors
      return Container(
            color: Colors.blue,
            child: Text('123dasdsadas', textDirection: TextDirection.rtl),
        )
    };

    /// Run the APPrunApp(MyApp()); },Object obj, StackTrace stack) {
    /// Collect error information for the Dart layer - upload the following logs to the server
    print(obj);
    print(stack);
  });
}
Copy the code

Model class

Asmcn. Icopy. Site/awesome/awe…

Juejin. Cn/post / 684490…

class AppAwesome {
  final String name;
  final String icon;

  AppAwesome({required this.name, required this.icon});

  AppAwesome.fromJson(Map<String.dynamic> json)
      : name = json['name'],
        icon = json['icon'];

  Map<String.dynamic> toJson() => {
        'name': name,
        'age': icon,
      };
}
Copy the code
Class AppConstant {static List<AppAwesome> appDart = [AppAwesome(name: 'wechat ', icon: 'assets/app/wechat ')]; }Copy the code

Image resources

www.cnblogs.com/mengqd/p/13…

  • Image.asset(String name) Retrieves the Image from AssetBundler
  • Image.network(String SRC) Displays the network image
  • Image.file(file file) Retrieves the Image from file
  • Image. Memory (Uint8List Bytes) Displays images from the Uint8List
{
    Key? key,
    AssetBundle? bundle,
    this.frameBuilder,// Call the frameBuilder when loading images. When null, this control will be displayed after loading images and blank when loading images, especially when loading network images. Therefore, this parameter can be used to handle transitions between displaying placeholder images and loading images, such as fading in and out. [Fade effect at the moment of coming out]
    this.errorBuilder,// A callback to the failed state frame constructor when loading an image
    this.semanticLabel,// Image semantic description.
    this.excludeFromSemantics = false.// Default false Whether to enable semantic description of images
    double? scale,// Scale
    this.width,// Specify the width of the display image area (not the width of the image)
    this.height,// Specify the height of the display image area (not the height of the image)
    this.color, // Color & colorBlendMode is used in conjunction with the background color
    this.colorBlendMode, // Color &colorBlendMode is used together to blend colors and images using a different algorithm to blend pixels. Different values of BlendMode specify different such algorithms. https://www.jianshu.com/p/4fb8f1a08d12 https://blog.csdn.net/chenlove1/article/details/84574237 https://www.cnblogs.com/mengqd/p/13145162.html
    this.fit,[BoxFit. Cover image can be stretched or clipped, but it fills the container.] [BoxFit. FitWidth] [BoxFit. FitWidth] [BoxFit. Cannot enlarge image]
    this.alignment = Alignment.center,// // Set the image alignment mode (centered by default)[bottomCenter: aligned at the bottom] [botomLeft: Bottom left aligned] [bottomRight: bottomRight aligned] [center: vertical and horizontal alignment] [centerLeft: vertical and horizontal alignment left aligned] [centerRight: vertical and horizontal alignment right aligned] [topLeft: Top left aligned] [topCenter: topCenter aligned] [topRight: top left aligned]
    this.repeat = ImageRepeat.noRepeat,[imagerepeat.repeat/imagerepeat.repeatx/imagerepeat.repeaty/imagerepeat.norepeat]
    this.centerSlice,// Set the centerSlice area in the image to.9 image and stretch the image as shown in.9 image.
    this.matchTextDirection = false.// when set to true, the picture will be drawn in the same direction as TextDirection. Its parent component must be Directionality :(default is false)
    this.gaplessPlayback = false.// Default false To continue to display the old image when the image provider changes, default not to display!
    this.isAntiAlias = false.// Whether to use anti-aliasing to draw images. The default is false
    String? package,
    this.filterQuality = FilterQuality.low,// filterQuality Indicates the quality of the image to draw. The value ranges from high->medium->low-> None. Of course, the performance loss is greater. The default value is low. You can set this parameter if the image is found to be jagged
    int? cacheWidth,CacheWidth or cacheheheight indicates to the image engine that the image should be decoded to the specified size; ---- specifies a small is vague
    int? cacheHeight,The cacheWidth or cacheheheight parameter is used to reduce the size of an image in memory;

  }
Copy the code
  • frameBuilder
Image.network( 'https://flutter.github.io/assets-for-api-docs/assets/widgets/puffin.jpg', frameBuilder: (BuildContext context, Widget child, int frame, bool wasSynchronouslyLoaded) { if (wasSynchronouslyLoaded) { return child; } return AnimatedOpacity( child: child, opacity: frame == null ? 0 : 1, duration: const Duration(seconds: 2), curve: Curves.easeOut, ); },)Copy the code
  • Reverse render matchTextDirection
Directionality(
  textDirection: TextDirection.rtl,
  child: Image.asset(
    'assets/images/logo.png',
    height: 150,
    matchTextDirection: true,
  ))
Copy the code

Local image

  • widget
Image.asset("asset/images/splash.jpeg")
Copy the code
  • pubspec.yaml
flutter:
  # The following line ensures that the Material Icons font is
  # included with your application, so that you can use the icons in
  # the material Icons class.
  uses-material-design: true

+ assets:
+ - assets/app/wechat.png
Copy the code

Network image

https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1005344786 and Image.net work (' 776190137 & FM = 26 & gp = 0. JPG ',)Copy the code