Introduction of Flutter

Flutter is Google’s open source mobile application development framework. Dart language is used as the development language, and its main features are cross-platform, high performance, and high fidelity. A single set of code runs on both Android and IOS and keeps the UI uniform (it works on the Web, too, but it’s not performing well so far).

How does Flutter cross platform and unify the UI? The key is that Google has implemented a cross-platform drawing engine, and the page we type is actually an image drawn by the drawing engine. (This is very similar to a game, and the game we see is rendered by the game engine. A less accurate analogy is that We all know that Java runs on most platforms, thanks to the fact that Java runs on a Java virtual machine, which makes the platform differences disappear). Of course, there are some disadvantages to this format, because our page is actually an image drawn by the drawing engine, so things like “select some text and copy” can be difficult to implement.

Why Flutter performance? Dart supports both JIT (just-in-time compilation, used by languages like JavaScript) and AOT (pre-compilation, used by languages like C++). Dart can be developed using the JIT instead of having to recompile every change, and released using AOT to compile ahead of time to speed up the program.

Is there a similar development framework? In fact, QT mobile, which works on a similar principle, was launched before Flutter, but it was not very popular due to poor official promotion and C++ ‘s high barrier to entry.

Introduction of the Dart

After working with Dart, the language feels like a hybrid of Java and JavaScript. Very similar to Java in static syntax, including type definitions, generics, etc. Dynamic syntax is very similar to JavaScript, functional features, asynchronous usage, etc. If you’re used to working with TS (and there’s a lot of Java and C# in TS), I’m sure the Dart language isn’t too difficult to learn.

Environment set up

Refer to the official website for a very detailed tutorial.

The key step is to install the Flutter SDK.

It is recommended to use Android Studio for the IDE, but it is also OK to install VS CODE plug-ins.

An introduction to

Start by comparing React.

If you’re familiar with React, it’s no surprise that you’ll feel a sense of immediateness when using a Flutter. In fact, the Flutter authorities mention that React was a factor in the design of the Flutter. For front-end developers familiar with React, it’s a relatively easy way to get started compared to React.

Both Flutter and React, as a declarative UI framework, follow the idea of UI = F (state). Plus, Flutter itself references React, so they have a lot in common. Let’s start our path to Flutter by writing a classic front-end entry application Todo List.

A brief design

Our Todo List is divided into two main pages, “Todo List Page” and “Todo Details Page”.

  1. The Todo list page is mainly used to display the Todo list.
  2. Todo details page is used to add Todo/ view Todo details.

Start coding

  1. The directory structure

There is nothing to enforce here; basically the code is in the lib directory. The directory structure here is mostly my development habit.

Flutter directory structure

├ ─ ─ lib / / equivalent to the React of SRC │ ├ ─ ─ app. The dart / / is equivalent to the React project app. Js │ ├ ─ ─. Main dart / / equivalent to React the index of the project. The js │ ├ ─ ─ models / / The MVC model simulates the model layer in the state management portion of the React │ │ ├ ─ ─ todo. Dart / / todo │ │ └ ─ ─ todo_list. Dart / / TodoList │ └ ─ ─ pages / / page │ ├ ─ ─ │ ├─ ├─ ├─ download.txt TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXT TXTCopy the code

Compare the React project directory structure

├ ─ ─ the SRC │ ├ ─ ─ App. Js │ ├ ─ ─ index. The js │ ├ ─ ─ models │ │ ├ ─ ─ todo. Js │ │ └ ─ ─ todoList. Js │ ├ ─ ─ pages │ │ ├ ─ ─ the detail │ │ │ └ ─ ─ index. Js │ │ └ ─ ─ the list │ │ └ ─ ─ index. The js ├ ─ ─ package. The jsonCopy the code
  1. Entrance to the file

Dart has a main function as an entry file. This main function also acts as an entry function for the entire application. The key function in Main is runApp, which is similar to reactdom.render in React.

import 'package:flutter/material.dart'; // Google's official component library, analogous to ANTD
import 'app.dart';

void main() {
  runApp(App());
}
Copy the code

Compare the React code

import React from 'react'

import ReactDOM from 'react-dom'

import App from './App'



ReactDOM.render(

    <App/>.document.getElementById('root'))Copy the code
  1. The Model layer design

TodoList applications have lists and Todo details, so we design two classes in this block, one TodoList class for lists and one Todo class for Todo details.

As for the React project, this design may not be very common, but it is consistent with Flutter for easy comparison.

Flutter Todo class code:

import 'package:uuid/uuid.dart';

class Todo {
  bool complete; // Todo completed state
  final String id; // Todo's unique ID
  final DateTime time; // Todo creation time
  String task; // Todo's specific task

  Todo({
    this.task,
    this.complete = false,
    DateTime time,
    String id
  }) : this.time = time ?? DateTime.now(), this.id = id ?? Uuid().v4();
}
Copy the code

React comparison code:

import {v4} from 'uuid';

class Todo {
    constructor(task, id = v4(), complete = false, time = new Date().toLocaleString()) {
        this.id = id
        this.task = task
        this.complete = complete
        this.time = time
    }
}

export default Todo
Copy the code

Flutter TodoList class code:

import 'package:flutter/foundation.dart';
import 'todo.dart' show Todo;

class TodoList with ChangeNotifier {
  Map<String, Todo> _list = new Map(a);// Used to save all toDos

  Map<String, Todo> get list => _list; // Getters for private variables

  void add(Todo todo) { / / add a todo
    _list[todo.id] = todo;
    notifyListeners(); // Notify the component of a state change
  }

  void remove(String id) { / / delete todo
    _list.remove(id);
    notifyListeners();
  }


  void statusChange(Todo todo) { // Change the todo statetodo.complete = ! todo.complete; _list.update(todo.id,(value) = > todo);
    notifyListeners();
  }

  Todo getById(String id) { // Get a single todo
    return_list[id]; }}Copy the code

React comparison code:

import React, {createContext} from 'react'

class TodoList {
    constructor() {
        this._list = new Map()}get list() {
        return this._list
    }


    add(todo) {
        this._list.set(todo.id, todo)
    }


    remove(id) {
        this._list.delete(id)
    }

    statusChange(todo) {
        this._list.set(todo.id, {... todo,complete: !todo.complete})
    }

    getById(id) {
        return this._list.get(id)
    }

}


export const todoList = new TodoList()
const TodoListContext = createContext(todoList)
export default TodoListContext
Copy the code
  1. Page routing

The route jump of a Flutter mainly uses Navigator, and the corresponding history of React.

There are several ways to route a page, refer to the official website for details. In contrast to React, named routes are introduced here.

import 'package:flutter/material.dart';
import 'pages/detail/index.dart';
import 'pages/list/index.dart';
import 'models/todo_list.dart';
import 'package:provider/provider.dart';

class App extends StatelessWidget {
  const App({Key key}) : super(key: key); 

  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (_) = > TodoList()),
      ],

      child: MaterialApp(
        title: 'flutter todo list'.initialRoute: '/'.routes: {

          '/': (context) = > ListPage(),

          '/list': (context) = > ListPage(),

          '/detail': (context) = > DetailPage(false),

          '/edit': (context) = > DetailPage(true),})); }}Copy the code

The React code

import {BrowserRouter, Switch, Route} from 'react-router-dom'
import TodoListContext, {todoList} from './models/todoList'
import DetailPage from './pages/detail'
import ListPage from './pages/list'
import './App.css'
import 'antd-mobile/dist/antd-mobile.css'

function App() {
    return (
        <TodoListContext.Provider value={todoList}>
            <BrowserRouter>
                <Switch>
                    <Route exact path="/" component={ListPage}/>
                    <Route exact path="/list" component={ListPage}/>
                    <Route exact path="/detail" component={DetailPage}/>
                    <Route exact path="/edit" component={DetailPage}/>
                </Switch>
            </BrowserRouter>
        </TodoListContext.Provider>)}export default App
Copy the code
  1. Write a component

Before writing the components, a brief description of the widgets inside Flutter. Similar to the idea that React pages are made up of components, Flutter pages are made up of widgets, so widgets can be colloquially understood as familiar components.

Flutter, like React, has stateless components and state components. This is achieved by inheriting StatelessWidget and StatefulWidget, respectively.

StatelessWidget, as you can tell by its name, is a stateless component. As the name suggests, React is used in scenarios where there is no need for internal component management of state. Front-end programmers who have written React should understand this. The code posted above to describe routing is a StatelessWidget.

StatefulWidget, this is the state component. A StatefulWidget corresponds to a State class, which is the State maintained by the State component. There are two common properties in State: Widget and context. The widget is the corresponding instance of this state component, which we typically use to get the properties defined in the StatefulWidget. Context is an instance of the BuildContext class, corresponding to the context of the component tree in which the component is located.

Normally, we would write one Widget and combine it with other widgets. Of course, if none of the existing widgets meet your needs, you can implement your own unique Widget. As I mentioned at the beginning, the page we write is really just an image drawn by the drawing engine. To implement a custom Widget on Flutter, draw the Widget using the CustomPainter class provided by Flutter (the CustomPainter is a Canvas).

Now that all the work is done, let’s start implementing the two most critical pages of the Todo List application.

Let’s start with the list page. The main functions of the list are Todo display and button to add Todo. The list page is mainly for presentation, and there is no need to maintain internal state, so we use the StatelessWidget.

Start with the most basic page structure:

class ListPage extends StatelessWidget {
  const ListPage({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) { // React class like Render
    
    return Scaffold( // Material component, the skeleton of the page

      appBar: AppBar( // The navigation bar in the header

        title: Text('Todo List'),

        leading: Container(), // Hide the left back button

      ),

      floatingActionButton: FloatingActionButton( // A floating button on the page

        child: Icon(Icons.add), // Button display icon

        onPressed: () {/* todo */}, // Click the event

      ),

      body: ListView.builder( // A list of page bodies

        itemCount: 0.// The total number of list items that the list contains

        itemBuilder: (context, index) { // Specify the list item component

          /* todo */

          returnContainer(); },),); }}Copy the code

React comparison code:

const ListPage = (props) = > {
    return (
        <div>
            <NavBar>Todo List</NavBar>
            <List>
                {/* todo */}
            </List>
            
            <Button
                onClick={()= > {}}
                icon={<Icon type="plus"/>} / ></div>)}export default ListPage
Copy the code

React might not look that similar to Flutter, but you wouldn’t if you’d seen the React code before JSX. Here is the React code without JSX for comparison.

const ListPage = (props) = > {
    return createElement(
        'div'.null,
        [
            createElement(
                NavBar,
                {key: 'listNavBar'},
                'Todo List',
            ),

            createElement(
                List,
                {key: 'listContent'},
            ),

            createElement(
                Button,
                {
                    key: 'listButton'.onClick: () = > {},
                    icon: createElement(
                        Icon,
                        {type: 'plus'})}),])}export default ListPage
Copy the code

This comparison shows that the use of Flutter components is similar to that of React. CreatElement. From this we can also see some disadvantages of Flutter, which is not intuitive to the front-end staff. Based on the principle of JSX, I think it is not strange that Flutter will be written similar to JSX in the future. I look forward to the DSX corresponding to DART.

From the above code we can get a page like this:

Let’s start implementing the functionality, starting with adding buttons. The Add button jumps to the page where the new Todo is added, so the button should have a callback to route the jump. Todo_list_react, todo_list_react_nojsx, todo_list_react_nojsx

floatingActionButton: FloatingActionButton(

    child: Icon(Icons.add),

    onPressed: () = > Navigator.of(context).pushNamed('/edit'), // Jump to the new todo page

),
Copy the code

Navigator.of(context).pushnamed (‘/edit’), this is similar to the familiar history.push(‘/edit’). But there’s one more. Of (context), what does that do? The “of” function is similar to the “bind” function in javascript. The “of” function is used to bind the context, which is the context of the Widget tree.

Next we implement the code for the list. The list item consists of toDO status, ToDO task, details button, and delete.

ListView.builder( // The official list component
  itemCount: list.list.length,
  itemBuilder: (context, index) {
    var v = list.list.values.toList()[index];
    return Card( // The official card component
      child: Dismissible( // The official gesture component
        key: Key(v.id),
        onDismissed: (direction) { // swipe callback to delete todo
          Scaffold.of(context).showSnackBar(SnackBar(content: Text('Delete task ${v.task}')));
          list.remove(v.id);
        },

        background: Container( // Swipe left/right to show delete icon
          color: Colors.red,
          child: ListTile(
            leading: Icon(
              Icons.delete,
              color: Colors.white,
            ),

            trailing: Icon(
              Icons.delete,
              color: Colors.white,
            ),
          ),
        ),

        child: ListTile( // The official list item component
          title: Row(
            children: [
              Icon(v.complete ? Icons.check_circle : Icons.access_time, color: v.complete ? Colors.green : Colors.red,),
              Container( // Todo completed state
                child: Padding(
                  padding: EdgeInsets.all(8.0),
                  child: v.complete ?
                  Text('Done'.style: TextStyle(color: Colors.white)) :
                  Text('not done'.style: TextStyle(color: Colors.white)),
                ),

                decoration: BoxDecoration(
                  color: Colors.blue,
                  borderRadius: BorderRadius.all(Radius.circular(5))),margin: EdgeInsets.only(right: 10.left: 10),
              ),

              Container( / / todo task
                width: 200.child: Text(v.task, overflow: TextOverflow.ellipsis, maxLines: 1),),),trailing: IconButton( // Details button
            icon: Icon(Icons.keyboard_arrow_right),

            onPressed: () = > Navigator.of(context).push(MaterialPageRoute(builder: (context) = > DetailPage(false.curId: v.id))),
          ),

          onTap: () { // Click callback to switch todo state
            onPressed: list.statusChange(v); },),),); },),Copy the code

With the above code, I introduce some basic widgets. Here I simply divided into the basic class, container class, layout class and function class to introduce (detailed API refer to the official website).

  1. Base classes, Image, Text, etc. These correspond to HTML tags like IMG, SPAN, etc
  2. Container class, Container, Padding these are Container class widgets. To make it easier to understand, think of it as an HTML tag like div.
  3. Layout class, Row, Column these are layout class widgets. These two are very similar to the flex layout we often use, where the layout is controlled by the main axis and the cross axis, where Row is the horizontal flex and Column is the vertical Flex.
  4. Features, Dismissible, Navigator and other widgets. These correspond to certain functions, such as gestures, routing, etc.

Here is a joke about the style writing of Flutter. Take a look at some style codes above. Compared with the CSS familiar to our front-end staff, the style writing of Flutter is obviously cumbersome and not intuitive. All the CSS preprocessors, whether native, CSS Module, CSS in JS or LESS, have a Flutter style.

After adding the list code, you get the following:

The page part is done, but where do we get our component data? This is going to go back to the part of the code that was routed above.

MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (_) = > TodoList()),
      ],
      // ...
);
Copy the code

The idea behind Flutter and React is that changes in state cause changes to the page. Flutter can also use some familiar state management libraries, Redux and Mobx. Use the official recommended Provider here.

We can actually compare the React Context. The above code is equivalent to:

<TodoListContext.Provider value={todoList}>

    {/ *... * /}

</TodoListContext.Provider>
Copy the code

The use state is similar for wrapped components.

Flutter:

TodoList list = Provider.of<TodoList>(context);
Copy the code

React:

const list = useContext(TodoListContext)
Copy the code

Slightly different from React, a Provider in Flutter notifies components manually.

class TodoList with ChangeNotifier { // ChangeNotifier notification class
  Map<String, Todo> _list = new Map(a);// Used to save all toDos
  Map<String, Todo> get list => _list; // Getters for private variables

  void add(Todo todo) { / / add a todo
    _list[todo.id] = todo;
    notifyListeners(); // A method to notify the component of a state change
  }
  // ...
}
Copy the code

A quick explanation of the above code. Dart has several ways of reusing code, extends, implements, and with. Inheritance is something you’re probably familiar with and I’m not going to do it here. Dart does not have an interface. Dart does not have an interface. This keyword is used to implement multiple abstract classes. Mixins are similar to mixins in Vue. I believe that we are familiar with them.

The list page is now complete. We started implementing the new TODO/TODO details page. Adding pages is easy, we just need an input box to enter the task and submit it. The details page is a display of todo information.

Because the new page/detail page requires state, we use the StatefulWidget to implement the page.

class DetailPage extends StatefulWidget {
  DetailPage(this._isCreate, {String curId}) {
    this._curId = curId; 
  }

  final bool _isCreate; // Whether todo is new
  String _curId; // The id of the todo when viewing the details page

  @override
  _DetailPageState createState() => _DetailPageState(); // All state components need to implement methods
}



class _DetailPageState extends State<DetailPage> { // Status Indicates the status of the component
  final _formKey = GlobalKey<FormState>(); // Compare the React Form ref
  bool _isCreate; // Whether to add todo
  String _task; / / todo task
  String _curId; // Current TOdo ID

  @override
  void initState() { // Initialize the life cycle
    // TODO: implement initState
 super.initState();
    _isCreate = widget._isCreate; // Widget is an instance of the StatefulWidget above
    _curId = widget._curId; // Used to get the properties declared by the StatefulWidget
  }



  @override
  Widget build(BuildContext context) {
    TodoList list = Provider.of<TodoList>(context);
    return Scaffold( // Page skeleton
      appBar: AppBar(
        title: _isCreate ? Text('new Todo) : Text('Todo Details Page '),
        leading: IconButton(
          icon: Icon(Icons.arrow_back_ios),
          onPressed: () = > Navigator.of(context).pushNamed('/'),),),body: _isCreate ? // Add page or detail page
      Form( / / form
        key: _formKey, // React ref
        child: Column(
          children: [
            TextFormField( / / input box
              decoration: InputDecoration(
                labelText: 'task'.prefixIcon: Icon(Icons.article),
              ),

              validator: (value) { // Check callback
                if (value == null || value.isEmpty) {
                  return 'Mandatory';
                }

                return null;
              },

              onSaved: (value) { // The callback triggered when the form is saved
                _task = value;
              },
            ),

            Padding(
              padding: const EdgeInsets.symmetric(vertical: 16.0),
              child: ElevatedButton( // Form submit button
                onPressed: () {
                  if (_formKey.currentState.validate()) { // The form is verified
                    _formKey.currentState.save(); / / save the field
                    list.add(new Todo( / / add a todo
                      task: _task,
                      time: new DateTime.now(),
                      complete: false)); Navigator.of(context).pushNamed('/'); // Route back to the list page}},child: Text('submit'(), () [(), (()// Details page code

      Column(
        children: [
          Card(
            child: Container(
              padding: EdgeInsets.all(16),
              width: MediaQuery.of(context).size.width,
              child: Row(
                children: [
                  Text('Task:'.style: TextStyle(fontSize: 16)),
                  Expanded(
                    child: Text('${list.getById(_curId).task}'.style: TextStyle(fontSize: 16)),
                  ),
                ],
              ),
            ),
          ),

          Card(
            child: Container(
              padding: EdgeInsets.all(16),
              child: Row(
                children: [
                  Text('Task Status:'.style: TextStyle(fontSize: 16)),
                  Text('${list.getById(_curId).complete ? 'Has been completed':'unfinished'} '.style: TextStyle(fontSize: 16)),
                ],
              ),
            ),
          ),

          Card(
            child: Container(
              padding: EdgeInsets.all(16),
              child: Row(
                children: [
                  Text('Task creation time:'.style: TextStyle(fontSize: 16), textAlign: TextAlign.left),
                  Text('${list.getById(_curId).time}'.style: TextStyle(fontSize: 16(() [(() [(() [() [() [() [() }}Copy the code

State has a life cycle in Flutter. Although the above code only uses initState, I think it is necessary to understand the specific life cycle of Flutter.

The main life cycles are described in the figure above.

  1. InitState, called when a Widget is first mounted into the Widget tree, compared to React componentDidMount.
  2. DidChangeDependencies, called when a dependency changes in the State object, which is triggered when the status is notified to the Widget as described above with the Provider.
  3. Build the Widget subtree compared to React render.
  4. Reassemble, development-specific, call back when hot reload.
  5. DidUpdateWidget, which detects whether the Widget is to be updated when the Widget is rebuilt. When the key and runtimeType of the old and new widgets are equal, the widget is still itself and needs to be updated, not uninstalled. The didUpdateWidget will then be invoked. The key of the Widget is compared to the key of the React component, and the runtimeType is compared to the type of the React. CreateElement (div becomes span).
  6. Deactive is called when a Widget is removed from the Widget tree and then inserted back into the Widget tree. The familiar concept is that a DOM node changes position in the DOM tree.
  7. Dispose, Widget is called when it is removed from the Widget tree. That is component uninstallation.

Complete the above code to get the following page:


Our Todo List application is now complete.

Learning Materials recommendation

The Real Flutter

Official website practical tutorial