返回介绍

Flutter for React Native developers

发布于 2019-12-09 21:31:21 字数 65371 浏览 922 评论 0 收藏 0

  • Checking for null or zero
  • Functions
  • Asynchronous programming
  • The basics
  • Project structure and resources
  • Flutter widgets
  • Views
  • Layouts
  • Styling
  • State Management
  • Props
  • Local storage
  • Routing
  • Gesture detection and touch event handling
  • Making HTTP network requests
  • Form input
  • Platform-specific code
  • Debugging
  • Animation
  • React Native and Flutter Widget equivalent components
  • This document is for React Native (RN) developers looking to apply their existing RN knowledge to build mobile apps with Flutter. If you understand the fundamentals of the RN framework then you can use this document as a way to get started learning Flutter development.

    This document can be used as a cookbook by jumping around and finding questions that are most relevant to your needs.

    Introduction to Dart for JavaScript Developers

    Like React Native, Flutter uses reactive-style views. However, while RN transpiles to native widgets, Flutter compiles all the way to native code. Flutter controls each pixel on the screen, which avoids performance problems caused by the need for a JavaScript bridge.

    Dart is an easy language to learn and offers the following features:

    • Provides an open-source, scalable programming language for building web, server, and mobile apps.
    • Provides an object-oriented, single inheritance language that uses a C-style syntax that is AOT-compiled into native.
    • Transcompiles optionally into JavaScript.
    • Supports interfaces and abstract classes.

    A few examples of the differences between JavaScript and Dart are described below.

    Entry point

    JavaScript doesn’t have a pre-defined entry function—you define the entry point.

    // JavaScript
    function startHere() {
      // Can be used as entry point
    }
    

    In Dart, every app must have a top-level main() function that serves as the entry point to the app.

    // Dart
    main() {
    }
    

    Try it out in DartPad.

    Printing to the console

    To print to the console in Dart, use print().

    // JavaScript
    console.log("Hello world!");
    
    // Dart
    print('Hello world!');
    

    Try it out in DartPad.

    Variables

    Dart is type safe—it uses a combination of static type checking and runtime checks to ensure that a variable’s value always matches the variable’s static type. Although types are mandatory, some type annotations are optional because Dart performs type inference.

    Creating and assigning variables

    In JavaScript, variables cannot be typed.

    In Dart, variables must either be explicitly typed or the type system must infer the proper type automatically.

    // JavaScript
    var name = "JavaScript";
    
    // Dart
    String name = 'dart'; // Explicitly typed as a string.
    var otherName = 'Dart'; // Inferred string.
    // Both are acceptable in Dart.
    

    Try it out in DartPad.

    For more information, see Dart’s Type System.

    Default value

    In JavaScript, uninitialized variables are undefined.

    In Dart, uninitialized variables have an initial value of null. Because numbers are objects in Dart, even uninitialized variables with numeric types have the value null.

    // JavaScript
    var name; // == undefined
    
    // Dart
    var name; // == null
    int x; // == null
    

    Try it out in DartPad.

    For more information, see the documentation on variables.

    Checking for null or zero

    In JavaScript, values of 1 or any non-null objects are treated as true.

    // JavaScript
    var myNull = null;
    if (!myNull) {
      console.log("null is treated as false");
    }
    var zero = 0;
    if (!zero) {
      console.log("0 is treated as false");
    }
    

    In Dart, only the boolean value true is treated as true.

    // Dart
    var myNull = null;
    if (myNull == null) {
      print('use "== null" to check null');
    }
    var zero = 0;
    if (zero == 0) {
      print('use "== 0" to check zero');
    }
    

    Try it out in DartPad.

    Functions

    Dart and JavaScript functions are generally similar. The primary difference is the declaration.

    // JavaScript
    function fn() {
      return true;
    }
    
    // Dart
    fn() {
      return true;
    }
    // can also be written as
    bool fn() {
      return true;
    }
    

    Try it out in DartPad.

    For more information, see the documentation on functions.

    Asynchronous programming

    Futures

    Like JavaScript, Dart supports single-threaded execution. In JavaScript, the Promise object represents the eventual completion (or failure) of an asynchronous operation and its resulting value.

    Dart uses Future objects to handle this.

    // JavaScript
    _getIPAddress = () => {
      const url="https://httpbin.org/ip";
      return fetch(url)
        .then(response => response.json())
        .then(responseJson => {
          console.log(responseJson.origin);
        })
        .catch(error => {
          console.error(error);
        });
    };
    
    // Dart
    _getIPAddress() {
      final url = 'https://httpbin.org/ip';
      HttpRequest.request(url).then((value) {
          print(json.decode(value.responseText)['origin']);
      }).catchError((error) => print(error));
    }
    

    Try it out in DartPad.

    For more information, see the documentation on Futures.

    async and await

    The async function declaration defines an asynchronous function.

    In JavaScript, the async function returns a Promise. The await operator is used to wait for a Promise.

    // JavaScript
    async _getIPAddress() {
      const url="https://httpbin.org/ip";
      const response = await fetch(url);
      const json = await response.json();
      const data = await json.origin;
      console.log(data);
    }
    

    In Dart, an async function returns a Future, and the body of the function is scheduled for execution later. The await operator is used to wait for a Future.

    // Dart
    _getIPAddress() async {
      final url = 'https://httpbin.org/ip';
      var request = await HttpRequest.request(url);
      String ip = json.decode(request.responseText)['origin'];
      print(ip);
    }
    

    Try it out in DartPad.

    For more information, see the documentation for async and await.

    The basics

    How do I create a Flutter app?

    To create an app using React Native, you would run create-react-native-app from the command line.

    $ create-react-native-app <projectname>

    To create an app in Flutter, do one of the following:

    • Use the flutter create command from the command line. Make sure that the Flutter SDK is in your PATH.
    • Use an IDE with the Flutter and Dart plugins installed.
    $ flutter create <projectname>

    For more information, see Getting Started, which walks you through creating a button-click counter app. Creating a Flutter project builds all the files that you need to run a sample app on both Android and iOS devices.

    How do I run my app?

    In React Native, you would run npm run or yarn run from the project directory.

    You can run Flutter apps in a couple of ways:

    • Use flutter run from the project’s root directory.
    • Use the “run” option in an IDE with the Flutter and Dart plugins.

    Your app runs on a connected device, the iOS simulator, or the Android emulator.

    For more information, see the Flutter Getting Started documentation.

    How do I import widgets?

    In React Native, you need to import each required component.

    //React Native
    import React from "react";
    import { StyleSheet, Text, View } from "react-native";
    

    In Flutter, to use widgets from the Material Design library, import the material.dart package. To use iOS style widgets, import the Cupertino library. To use a more basic widget set, import the Widgets library. Or, you can write your own widget library and import that.

    import 'package:flutter/material.dart';
    import 'package:flutter/cupertino.dart';
    import 'package:flutter/widgets.dart';
    import 'package:flutter/my_widgets.dart';
    

    Whichever widget package you import, Dart pulls in only the widgets that are used in your app.

    For more information, see the Flutter Widgets Catalog.

    What is the equivalent of the React Native “Hello world!” app in Flutter?

    In React Native, the HelloWorldApp class extends React.Component and implements the render method by returning a view component.

    // React Native
    import React from "react";
    import { StyleSheet, Text, View } from "react-native";
    
    export default class App extends React.Component {
      render() {
        return (
          <View style={styles.container}>
            <Text>Hello world!</Text>
          </View>
        );
      }
    }
    
    const styles = StyleSheet.create({
      container: {
        flex: 1,
        backgroundColor: "#fff",
        alignItems: "center",
        justifyContent: "center"
      }
    });
    

    In Flutter, you can create an identical “Hello world!” app using the Center and Text widgets from the core widget library. The Center widget becomes the root of the widget tree and has one child, the Text widget.

    // Flutter
    import 'package:flutter/material.dart';
    
    void main() {
      runApp(
        Center(
          child: Text(
            'Hello, world!',
            textDirection: TextDirection.ltr,
          ),
        ),
      );
    }
    
    

    The following images show the Android and iOS UI for the basic Flutter “Hello world!” app.

    Hello world app on Android
    Android
    Hello world app on iOS
    iOS

    Now that you’ve seen the most basic Flutter app, the next section shows how to take advantage of Flutter’s rich widget libraries to create a modern, polished app.

    How do I use widgets and nest them to form a widget tree?

    In Flutter, almost everything is a widget.

    Widgets are the basic building blocks of an app’s user interface. You compose widgets into a hierarchy, called a widget tree. Each widget nests inside a parent widget and inherits properties from its parent. Even the application object itself is a widget. There is no separate “application” object. Instead, the root widget serves this role.

    A widget can define:

    • A structural element—like a button or menu
    • A stylistic element—like a font or color scheme
    • An aspect of layout—like padding or alignment

    The following example shows the “Hello world!” app using widgets from the Material library. In this example, the widget tree is nested inside the MaterialApp root widget.

    // Flutter
    import 'package:flutter/material.dart';
    
    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Welcome to Flutter',
          home: Scaffold(
            appBar: AppBar(
              title: Text('Welcome to Flutter'),
            ),
            body: Center(
              child: Text('Hello world'),
            ),
          ),
        );
      }
    }
    
    

    The following images show “Hello world!” built from Material Design widgets. You get more functionality for free than in the basic “Hello world!” app.

    Hello world app on Android
    Android
    Hello world app on iOS
    iOS

    When writing an app, you’ll use two types of widgets: StatelessWidget or StatefulWidget. A StatelessWidget is just what it sounds like—a widget with no state. A StatelessWidget is created once, and never changes its appearance. A StatefulWidget dynamically changes state based on data received, or user input.

    The important difference between stateless and stateful widgets is that StatefulWidgets have a State object that stores state data and carries it over across tree rebuilds, so it’s not lost.

    In simple or basic apps it’s easy to nest widgets, but as the code base gets larger and the app becomes complex, you should break deeply nested widgets into functions that return the widget or smaller classes. Creating separate functions and widgets allows you to reuse the components within the app.

    How do I create reusable components?

    In React Native, you would define a class to create a reusable component and then use props methods to set or return properties and values of the selected elements. In the example below, the CustomCard class is defined and then used inside a parent class.

    // React Native
    class CustomCard extends React.Component {
      render() {
        return (
          <View>
            <Text> Card {this.props.index} </Text>
            <Button
              title="Press"
              onPress={() => this.props.onPress(this.props.index)}
            />
          </View>
        );
      }
    }
    
    // Usage
    <CustomCard onPress={this.onPress} index={item.key} />
    

    In Flutter, define a class to create a custom widget and then reuse the widget. You can also define and call a function that returns a reusable widget as shown in the build function in the following example.

    // Flutter
    class CustomCard extends StatelessWidget {
      CustomCard({@required this.index, @required 
         this.onPress});
    
      final index;
      final Function onPress;
    
      @override
      Widget build(BuildContext context) {
        return Card(
          child: Column(
            children: <Widget>[
              Text('Card $index'),
              FlatButton(
                child: const Text('Press'),
                onPressed: this.onPress,
              ),
            ],
          )
        );
      }
    }
        ...
    // Usage
    CustomCard(
      index: index,
      onPress: () { 
        print('Card $index');
      },
    )
        ...

    In the previous example, the constructor for the CustomCard class uses Dart’s curly brace syntax { } to indicate named optional parameters.

    To require these fields, either remove the curly braces from the constructor, or add @required to the constructor.

    The following screenshots show an example of the reusable CustomCard class.

    Custom cards on Android
    Android
    Custom cards on iOS
    iOS

    Project structure and resources

    Where do I start writing the code?

    Start with the main.dart file. It’s autogenerated when you create a Flutter app.

    // Dart
    void main(){
     print("Hello, this is the main function.");
    }
    

    In Flutter, the entry point file is ’projectname’/lib/main.dart and execution starts from the main function.

    How are files structured in a Flutter app?

    When you create a new Flutter project, it builds the following directory structure. You can customize it later, but this is where you start.

    ┬
    └ projectname
      ┬
      ├ android      - Contains Android-specific files.
      ├ build        - Stores iOS and Android build files.
      ├ ios          - Contains iOS-specific files.
      ├ lib          - Contains externally accessible Dart source files.
        ┬
        └ src        - Contains additional source files.
        └ main.dart  - The Flutter entry point and the start of a new app.
                       This is generated automatically when you create a Flutter
                        project.
                       It's where you start writing your Dart code.
      ├ test         - Contains automated test files.
      └ pubspec.yaml - Contains the metadata for the Flutter app.
                       This is equivalent to the package.json file in React Native.
    

    Where do I put my resources and assets and how do I use them?

    A Flutter resource or asset is a file that is bundled and deployed with your app and is accessible at runtime. Flutter apps can include the following asset types:

    • Static data such as JSON files
    • Configuration files
    • Icons and images (JPEG, PNG, GIF, Animated GIF, WebP, Animated WebP, BMP, and WBMP)

    Flutter uses the pubspec.yaml file, located at the root of your project, to identify assets required by an app.

    flutter:
      assets:
        - assets/my_icon.png
        - assets/background.png
    

    The assets subsection specifies files that should be included with the app. Each asset is identified by an explicit path relative to the pubspec.yaml file, where the asset file is located. The order in which the assets are declared does not matter. The actual directory used (assets in this case) does not matter. However, while assets can be placed in any app directory, it’s a best practice to place them in the assets directory.

    During a build, Flutter places assets into a special archive called the asset bundle, which apps read from at runtime. When an asset’s path is specified in the assets section of pubspec.yaml, the build process looks for any files with the same name in adjacent subdirectories. These files are also included in the asset bundle along with the specified asset. Flutter uses asset variants when choosing resolution-appropriate images for your app.

    In React Native, you would add a static image by placing the image file in a source code directory and referencing it.

    <Image source={require("./my-icon.png")} />
    

    In Flutter, add a static image to your app using the AssetImage class in a widget’s build method.

    image: AssetImage('assets/background.png'),
    

    For more information, see Adding Assets and Images in Flutter.

    How do I load images over a network?

    In React Native, you would specify the uri in the source prop of the Image component and also provide the size if needed.

    In Flutter, use the Image.network constructor to include an image from a URL.

    // Flutter
    body: Image.network(
              'https://flutter.io/images/owl.jpg',
    

    How do I install packages and package plugins?

    Flutter supports using shared packages contributed by other developers to the Flutter and Dart ecosystems. This allows you to quickly build your app without having to develop everything from scratch. Packages that contain platform-specific code are known as package plugins.

    In React Native, you would use yarn add {package-name} or npm install --save {package-name} to install packages from the command line.

    In Flutter, install a package using the following instructions:

    1. Add the package name and version to the pubspec.yaml dependencies section. The example below shows how to add the google_sign_in Dart package to the pubspec.yaml file. Check your spaces when working in the YAML file because white space matters!
    dependencies:
      flutter:
        sdk: flutter
      google_sign_in: ^3.0.3
    
    1. Install the package from the command line by using flutter packages get. If using an IDE, it often runs flutter packages get for you, or it might prompt you to do so.
    2. Import the package into your app code as shown below:
    import 'package:flutter/cupertino.dart';
    

    For more information, see Using Packages and Developing Packages & Plugins.

    You can find many packages shared by Flutter developers in the Flutter Packages section of the Pub site.

    Flutter widgets

    In Flutter, you build your UI out of widgets that describe what their view should look like given their current configuration and state.

    Widgets are often composed of many small, single-purpose widgets that are nested to produce powerful effects. For example, the Container widget consists of several widgets responsible for layout, painting, positioning, and sizing. Specifically, the Container widget includes the LimitedBox, ConstrainedBoxAlign, PaddingDecoratedBox, and Transform widgets. Rather than subclassing Container to produce a customized effect, you can compose these and other simple widgets in new and unique ways.

    The Center widget is another example of how you can control the layout. To center a widget, wrap it in a Center widget and then use layout widgets for alignment, row, columns, and grids. These layout widgets do not have a visual representation of their own. Instead, their sole purpose is to control some aspect of another widget’s layout. To understand why a widget renders in a certain way, it’s often helpful to inspect the neighboring widgets.

    For more information, see the Flutter Technical Overview.

    For more information about the core widgets from the Widgets package, see Flutter Basic Widgets, the Flutter Widget Catalog, or the Flutter Widget Index.

    Views

    What is the equivalent of the View container?

    In React Native, View is a container that supports layout with Flexbox, style, touch handling, and accessibility controls.

    In Flutter, you can use the core layout widgets in the Widgets library, such as Container, Column, Row, and Center.

    For more information, see the Layout Widgets catalog.

    What is the equivalent of FlatList or SectionList?

    A List is a scrollable list of components arranged vertically.

    In React Native, FlatList or SectionList are used to render simple or sectioned lists.

    // React Native
    <FlatList
      data={[ ... ]}
      renderItem={({ item }) => <Text>{item.key}</Text>}
    />
    

    ListView is Flutter’s most commonly used scrolling widget. The default constructor takes an explicit list of children. ListView is most appropriate for a small number of widgets. For a large or infinite list, use ListView.builder, which builds its children on demand and only builds those children that are visible.

    // Flutter
    var data = [ ... ];
    ListView.builder(
      itemCount: data.length,
      itemBuilder: (context, int index) {
        return Text(
          data[index],
        );
      },
    )
    
    Flat list on Android
    Android
    Flat list on iOS
    iOS

    To learn how to implement an infinite scrolling list, see the Write Your First Flutter App, Part 1 codelab.

    How do I use a Canvas to draw or paint?

    In React Native, canvas components aren’t present so third party libraries like react-native-canvas are used.

    // React Native
    handleCanvas = canvas => {
      const ctx = canvas.getContext("2d");
      ctx.fillStyle = "skyblue";
      ctx.beginPath();
      ctx.arc(75, 75, 50, 0, 2 * Math.PI);
      ctx.fillRect(150, 100, 300, 300);
      ctx.stroke();
    };
    
    render() {
      return (
        <View>
          <Canvas ref={this.handleCanvas} />
        </View>
      );
    }
    

    In Flutter, you can use the CustomPaint and CustomPainter classes to draw to the canvas.

    The following example shows how to draw during the paint phase using the CustomPaint widget. It implements the abstract class, CustomPainter, and passes it to CustomPaint’s painter property. CustomPaint subclasses must implement the paint and shouldRepaint methods.

    // Flutter
    class MyCanvasPainter extends CustomPainter {
    
      @override
      void paint(Canvas canvas, Size size) {
        Paint paint = Paint();
        paint.color = Colors.amber;
        canvas.drawCircle(Offset(100.0, 200.0), 40.0, paint);
        Paint paintRect = Paint();
        paintRect.color = Colors.lightBlue;
        Rect rect = Rect.fromPoints(Offset(150.0, 300.0), Offset(300.0, 400.0));
        canvas.drawRect(rect, paintRect);
      }
    
      bool shouldRepaint(MyCanvasPainter oldDelegate) => false;
      bool shouldRebuildSemantics(MyCanvasPainter oldDelegate) => false;
    }
    class _MyCanvasState extends State<MyCanvas> {
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: CustomPaint(
            painter: MyCanvasPainter(),
          ),
        );
      }
    }
    
    Canvas on Android
    Android
    Canvas on iOS
    iOS

    Layouts

    How do I use widgets to define layout properties?

    In React Native, most of the layout can be done with the props that are passed to a specific component. For example, you could use the style prop on the View component in order to specify the flexbox properties. To arrange your components in a column, you would specify a prop such as: flexDirection: “column”.

    // React Native
    <View
      style={{
        flex: 1,
        flexDirection: "column",
        justifyContent: "space-between",
        alignItems: "center"
      }}
    >
    

    In Flutter, the layout is primarily defined by widgets specifically designed to provide layout, combined with control widgets and their style properties.

    For example, the Column and Row widgets take an array of children and align them vertically and horizontally respectively. A Container widget takes a combination of layout and styling properties, and a Center widget centers its child widgets.

    // Flutter
    Center(
      child: Column(
        children: <Widget>[
          Container(
            color: Colors.red,
            width: 100.0,
            height: 100.0,
          ),
          Container(
            color: Colors.blue,
            width: 100.0,
            height: 100.0,
          ),
          Container(
            color: Colors.green,
            width: 100.0,
            height: 100.0,
          ),
        ],
      ),
    )
    

    Flutter provides a variety of layout widgets in its core widget library. For example, Padding, Align, and Stack.

    For a complete list, see Layout Widgets.

    Layout on Android
    Android
    Layout on iOS
    iOS

    How do I layer widgets?

    In React Native, components can be layered using absolute positioning.

    Flutter uses the Stack widget to arrange children widgets in layers. The widgets can entirely or partially overlap the base widget.

    The Stack widget positions its children relative to the edges of its box. This class is useful if you simply want to overlap several children widgets.

    // Flutter
    Stack(
      alignment: const Alignment(0.6, 0.6),
      children: <Widget>[
        CircleAvatar(
          backgroundImage: NetworkImage(
            "https://avatars3.githubusercontent.com/u/14101776?v=4"),
        ),
        Container(
          decoration: BoxDecoration(
              color: Colors.black45,
          ),
          child: Text('Flutter'),
        ),
      ],
    )
    

    The previous example uses Stack to overlay a Container (that displays its Text on a translucent black background) on top of a CircleAvatar. The Stack offsets the text using the alignment property and Alignment coordinates.

    Stack on Android
    Android
    Stack on iOS
    iOS

    For more information, see the Stack class documentation.

    Styling

    How do I style my components?

    In React Native, inline styling and stylesheets.create are used to style components.

    // React Native
    <View style={styles.container}>
      <Text style={{ fontSize: 32, color: "cyan", fontWeight: "600" }}>
        This is a sample text
      </Text>
    </View>
    
    const styles = StyleSheet.create({
      container: {
        flex: 1,
        backgroundColor: "#fff",
        alignItems: "center",
        justifyContent: "center"
      }
    });
    

    In Flutter, a Text widget can take a TextStyle class for its style property. If you want to use the same text style in multiple places, you can create a TextStyle class and use it for multiple Text widgets.

    // Flutter
    var textStyle = TextStyle(fontSize: 32.0, color: Colors.cyan, fontWeight:
       FontWeight.w600);
    	...
    Center(
      child: Column(
        children: <Widget>[
          Text(
            'Sample text',
            style: textStyle,
          ),
          Padding(
            padding: EdgeInsets.all(20.0),
            child: Icon(Icons.lightbulb_outline,
              size: 48.0, color: Colors.redAccent)
          ),
        ],
      ),
    )
    
    Styling on Android
    Android
    Styling on iOS
    iOS

    How do I use Icons and Colors?

    React Native doesn’t include support for icons so third party libraries are used.

    In Flutter, importing the Material library also pulls in the rich set of Material icons and colors.

    Icon(Icons.lightbulb_outline, color: Colors.redAccent)
    

    When using the Icons class, make sure to set uses-material-design: true in the project’s pubspec.yaml file. This ensures that the MaterialIcons font, which displays the icons, is included in your app.

    name: my_awesome_application
    flutter: uses-material-design: true

    Flutter’s Cupertino (iOS-style) package provides high fidelity widgets for the current iOS design language. To use the CupertinoIcons font, add a dependency for cupertino_icons in your project’s  pubspec.yaml file.

    name: my_awesome_application
    dependencies:
      cupertino_icons: ^0.1.0
    

    To globally customize the colors and styles of components, use ThemeData to specify default colors for various aspects of the theme. Set the theme property in MaterialApp to the ThemeData object. The Colors class provides colors from the Material Design color palette.

    The following example sets the primary swatch to blue and the text selection to red.

    class SampleApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Sample App',
          theme: ThemeData(
            primarySwatch: Colors.blue,
            textSelectionColor: Colors.red
          ),
          home: SampleAppPage(),
        );
      }
    }

    How do I add style themes?

    In React Native, common themes are defined for components in stylesheets and then used in components.

    In Flutter, create uniform styling for almost everything by defining the styling in the ThemeData class and passing it to the theme property in the MaterialApp widget.

      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          theme: ThemeData(
            primaryColor: Colors.cyan,
            brightness: Brightness.dark,
          ),
          home: StylingPage(),
        );
      }
    

    A Theme can be applied even without using the MaterialApp widget. The Theme widget takes a ThemeData in its data parameter and applies the ThemeData to all of its children widgets.

     @override
      Widget build(BuildContext context) {
        return Theme(
          data: ThemeData(
            primaryColor: Colors.cyan,
            brightness: brightness,
          ),
          child: Scaffold(
             backgroundColor: Theme.of(context).primaryColor,
                  ...
                  ...
          ),
        );
      }
    

    State Management

    State is information that can be read synchronously when a widget is built or information that might change during the lifetime of a widget. To manage app state in Flutter, use a StatefulWidget paired with a State object.

    The StatelessWidget

    A StatelessWidget in Flutter is a widget that doesn’t require a state change—it has no internal state to manage.

    Stateless widgets are useful when the part of the user interface you are describing does not depend on anything other than the configuration information in the object itself and the BuildContext in which the widget is inflated.

    AboutDialog, CircleAvatar, and Text are examples of stateless widgets which subclass StatelessWidget.

    // Flutter
    import 'package:flutter/material.dart';
    
    void main() => runApp(MyStatelessWidget(text: "StatelessWidget Example to show immutable data"));
    
    class MyStatelessWidget extends StatelessWidget {
      final String text;
      MyStatelessWidget({Key key, this.text}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return Center(
          child: Text(
            text,
            textDirection: TextDirection.ltr,
          ),
        );
      }
    }
    

    In the previous example, you used the constructor of the MyStatelessWidget class to pass the text, which is marked as final. This class extends StatelessWidget—it contains immutable data.

    The build method of a stateless widget is typically called in only three situations:

    • When the widget is inserted into a tree
    • When the widget’s parent changes its configuration
    • When an InheritedWidget it depends on, changes

    The StatefulWidget

    A StatefulWidget is a widget that changes state. Use the setState method to manage the state changes for a StatefulWidget. A call to setState tells the Flutter framework that something has changed in a state, which causes an app to rerun the build method so that the app can reflect the change.

    State is information that can be read synchronously when a widget is built and might change during the lifetime of the widget. It’s the responsibility of the widget implementer to ensure that the state is promptly notified when the state changes. Use StatefulWidget when a widget can change dynamically. For example, the state of the widget changes by typing into a form, or moving a slider. Or, it can change over time—perhaps a data feed updates the UI.

    Checkbox, Radio, Slider, InkWell, Form, and TextField are examples of stateful widgets, that subclass StatefulWidget.

    The following example declares a StatefulWidget which requires a createState() method. This method creates the state object that manages the widget’s state, _MyStatefulWidgetState.

    class MyStatefulWidget extends StatefulWidget {
      MyStatefulWidget({Key key, this.title}) : super(key: key);
      final String title;
    
      @override
      _MyStatefulWidgetState createState() => _MyStatefulWidgetState();
    }
    

    The following state class, _MyStatefulWidgetState, implements the build() method for the widget. When the state changes, for example, when the user toggles the button, setState is called with the new toggle value. This causes the framework to rebuild this widget in the UI.

    class _MyStatefulWidgetState extends State<MyStatefulWidget> {
      bool showtext=true;
      bool toggleState=true;
      Timer t2;
    
      void toggleBlinkState(){
        setState((){
          toggleState=!toggleState;
        });
        var twenty = const Duration(milliseconds: 1000);
        if(toggleState==false) {
          t2 = Timer.periodic(twenty, (Timer t) {
            toggleShowText();
          });
        } else {
          t2.cancel();
        }
      }
    
      void toggleShowText(){
        setState((){
          showtext=!showtext;
        });
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: Center(
            child: Column(
              children: <Widget>[
                (showtext
                  ?(Text('This execution will be done before you can blink.'))
                  :(Container())
                ),
                Padding(
                  padding: EdgeInsets.only(top: 70.0),
                  child: RaisedButton(
                    onPressed: toggleBlinkState,
                    child: (toggleState
                      ?( Text('Blink'))
                      :(Text('Stop Blinking'))
                    )
                  )
                )
              ],
            ),
          ),
        );
      }
    }
    

    What are the StatefulWidget and StatelessWidget best practices?

    Here are a few things to consider when designing your widget.

    1. Determine whether a widget should be a StatefulWidget or a StatelessWidget

    In Flutter, widgets are either Stateful or Stateless—depending on whether they depend on a state change.

    • If a widget changes—the user interacts with it or a data feed interrupts the UI, then it’s Stateful.
    • If a widget is final or immutable, then it’s Stateless.

    2. Determine which object manages the widget’s state (for a StatefulWidget)

    In Flutter, there are three primary ways to manage state:

    • The widget manages its own state
    • The parent widget manages the widget’s state
    • A mix-and-match approach

    When deciding which approach to use, consider the following principles:

    • If the state in question is user data, for example the checked or unchecked mode of a checkbox, or the position of a slider, then the state is best managed by the parent widget.
    • If the state in question is aesthetic, for example an animation, then the widget itself best manages the state.
    • When in doubt, let the parent widget manage the child widget’s state.

    3. Subclass StatefulWidget and State

    The MyStatefulWidget class manages its own state—it extends StatefulWidget, it overrides the createState() method to create the State object, and the framework calls createState() to build the widget. In this example, createState() creates an instance of _MyStatefulWidgetState, which is implemented in the next best practice.

    class MyStatefulWidget extends StatefulWidget {
      MyStatefulWidget({Key key, this.title}) : super(key: key);
      final String title;
    
      @override
      _MyStatefulWidgetState createState() => _MyStatefulWidgetState();
    }
    
    class _MyStatefulWidgetState extends State<MyStatefulWidget> {
    
      @override
      Widget build(BuildContext context) {
        ...
      }
    }
    

    4. Add the StatefulWidget into the widget tree

    Add your custom StatefulWidget to the widget tree in the app’s build method.

    class MyStatelessWidget extends StatelessWidget {
      // This widget is the root of your application.
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Flutter Demo',
          theme: ThemeData(
            primarySwatch: Colors.blue,
          ),
          home: MyStatefulWidget(title: 'State Change Demo'),
        );
      }
    }
    
    State change on Android
    Android
    State change on iOS
    iOS

    Props

    In React Native, most components can be customized when they are created with different parameters or properties, called props. These parameters can be used in a child component using this.props.

    // React Native
    class CustomCard extends React.Component {
      render() {
        return (
          <View>
            <Text> Card {this.props.index} </Text>
            <Button
              title="Press"
              onPress={() => this.props.onPress(this.props.index)}
            />
          </View>
        );
      }
    }
    class App extends React.Component {
    
      onPress = index => {
        console.log("Card ", index);
      };
    
      render() {
        return (
          <View>
            <FlatList
              data={[ ... ]}
              renderItem={({ item }) => (
                <CustomCard onPress={this.onPress} index={item.key} />
              )}
            />
          </View>
        );
      }
    }
    

    In Flutter, you assign a local variable or function marked final with the property received in the parameterized constructor.

    // Flutter
    class CustomCard extends StatelessWidget {
    
      CustomCard({@required this.index, @required this.onPress});
      final index;
      final Function onPress;
    
      @override
      Widget build(BuildContext context) {
      return Card(
        child: Column(
          children: <Widget>[
            Text('Card $index'),
            FlatButton(
              child: const Text('Press'),
              onPressed: this.onPress,
            ),
          ],
        ));
      }
    }
        ...
    //Usage
    CustomCard(
      index: index,
      onPress: () {
        print('Card $index');
      },
    )
    
    Cards on Android
    Android
    Cards on iOS
    iOS

    Local storage

    If you don’t need to store a lot of data and it doesn’t require structure, you can use shared_preferences which allows you to read and write persistent key-value pairs of primitive data types: booleans, floats, ints, longs, and strings.

    How do I store persistent key-value pairs that are global to the app?

    In React Native, you use the setItem and getItem functions of the AsyncStorage component to store and retrieve data that is persistent and global to the app.

    // React Native
    await AsyncStorage.setItem( "counterkey", json.stringify(++this.state.counter));
    AsyncStorage.getItem("counterkey").then(
                      
                      
                    

    如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

    扫码二维码加入Web技术交流群

    发布评论

    需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
    列表为空,暂无数据
      我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
      原文