05 Experiencing Flutter Code Using Standard Templates on Native Systems

05 Experiencing Flutter Code Using Standard Templates on Native Systems #

Hello, I’m Chen Hang.

In the first pre-learning article of this column, we built the development environment for Flutter together, and I showed you how Flutter projects run on Android and iOS emulators as well as real devices using the built-in hello_world example.

Today, I will use the Flutter application template created by Android Studio to help you understand the project structure of Flutter, analyze the connections between Flutter projects and native Android and iOS projects, and experience how a Flutter application with basic functionality operates. This will deepen your understanding of key concepts and techniques for building Flutter applications.

If you are not familiar with the Dart language yet, don’t worry. As long as you can understand basic programming concepts (such as types, variables, functions, and object-oriented programming) and have some front-end knowledge (such as understanding what a View is and basic page layout), you can complete today’s learning with me. I will discuss the basic concepts of the Dart language and provide case studies in the next module.

Counter Example Project Analysis #

Firstly, we open Android Studio and create a Flutter project called flutter_app. Flutter will automatically generate a simple counter example application demo based on its default application template. Let’s run this example first, and the effect is as follows:

Figure 1 Counter Example Running Effect

Every time we click the floating button with a “+” sign in the lower right corner, the number in the center of the screen will increase by 1.

Project Structure #

After experiencing the running effect of the example project, let’s take a look at the directory structure of the Flutter project to understand the relationship between the Flutter project and the native Android and iOS projects, as well as how these relationships ensure that a Flutter program can eventually run on the Android and iOS systems.

Figure 2 Flutter Project Directory Structure

As can be seen, in addition to Flutter’s own code, resources, dependencies, and configurations, the Flutter project also includes the Android and iOS project directories.

This is not difficult to understand because although Flutter is a cross-platform development solution, it needs a container to run on the Android and iOS platforms. Therefore, the Flutter project is actually a parent project that embeds the native Android and iOS sub-projects at the same time: we develop Flutter code in the lib directory, and provide corresponding code implementations for certain special native functionalities in the corresponding Android and iOS projects for the Flutter code to reference.

Flutter will inject the related dependencies and build artifacts into these two sub-projects, which will ultimately be integrated into their respective projects. And the Flutter code we develop will eventually run in the form of native projects.

Project Code #

After having a preliminary understanding of the Flutter project structure, we can start learning the Flutter project code.

The built-in application template of Flutter, which is this counter example, is an excellent entry example for beginners. In this simple example, the core thoughts of Flutter are demonstrated in these over 60 lines of code, from basic components, layouts, gesture listeners to state changes.

In order to facilitate your learning and understanding of the overall ideas and key technologies of building Flutter programs, instead of getting stuck in the API details of components at the beginning, I removed the component configuration code and layout logic that is not relevant to the core process, made modifications to the code without affecting the functionality of the example, and divided it into two parts:

  * The first part is the application entry, application structure, and page structure, which can help you understand the basic structure and routines of building Flutter programs;

  * The second part is the page layout, interaction logic, and state management, which can help you understand how Flutter pages are constructed, how they respond to interactions, and how they are updated.

Firstly, let’s take a look at the code of the first part, which is the overall structure of the application:

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) => MaterialApp(home: MyHomePage(title: 'Flutter Demo Home Page'));
}

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

class _MyHomePageState extends State<MyHomePage> {
  Widget build(BuildContext context) => {...};
}

In this example, the Flutter application is an instance of the MyApp class, which inherits from the StatelessWidget class. This means that the application itself is also a Widget. In fact, in Flutter, the concept of application, view, view controller, layout, etc., including application, view, view controller, and layout, are all built on top of Widget. The core design principle of Flutter is that everything is a Widget.

A Widget is an encapsulation of the visual effect of a component, which is the carrier of the UI interface. Therefore, we also need to provide a method for it to tell the Flutter framework how to construct the UI interface, and this method is called build.

In the build method, we usually configure the basic Widget according to the corresponding UI, or customize the UI by combining various basic Widgets. For example, in MyApp, I set the home page of the application, which is MyHomePage, through the MaterialApp framework, which is also a Widget.

The MaterialApp class is a component encapsulation framework for building material design style applications. It has many configurable properties, such as application theme, application name, language identifier, component routing, etc. However, these configuration properties are not the focus of this article. If you are interested, you can refer to the API documentation of Flutter officially to learn about other configuration capabilities of the MaterialApp framework.

MyHomePage is the home page of the application, inherited from the StatefulWidget class. This means that it is a stateful Widget, and _MyHomePageState is its state.

If you are careful enough, you will notice that although MyHomePage is also a Widget, unlike MyApp, it does not have a build method to return a Widget, but instead has a createState method that returns an _MyHomePageState object, and the build method is included in this _MyHomePageState class.

So why is there such a difference in the interface design between StatefulWidget and StatelessWidget?

This is because a Widget needs data to be constructed, and for a StatefulWidget, the data it depends on may change frequently during the Widget’s lifecycle. State creates Widgets, updates views based on data, rather than directly manipulating UI to update visual properties. This allows for concise code expression and clearer logic.

After understanding the overall structure of the counter example program, let’s take a look at the second part of the example code, which is the page layout and interaction logic part.

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;
  void _incrementCounter() => setState(() {_counter++;});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(Widget.title)),
      body: Text('You have pushed the button this many times:$_counter')),
      floatingActionButton: FloatingActionButton(onPressed: _incrementCounter) 
    );
  }
}

The Widget Scaffold created in _MyHomePageState is a page layout structure provided by the Material library, which includes AppBar, Body, and FloatingActionButton.

  • AppBar is the navigation bar of the page, and we directly use the title attribute of MyHomePage as the title.
  • The body is a Text component that displays a text that can change based on the _counter attribute: ‘You have pushed the button this many times:$_counter’.
  • The floatingActionButton is a floating button with a “+” sign in the lower right corner of the page. We use _incrementCounter as its click handler.

The implementation of _incrementCounter is very simple, using the setState method to increment the state attribute _counter. The setState method is a key function in Flutter that drives the view update based on the data. It notifies the Flutter framework: I have changed the state here, please refresh the interface for me. When the Flutter framework receives the notification, it will execute the build method of the Widget and rebuild the UI based on the new state.

It is important to note here that: the modification of the state must be accompanied by the use of setState. By calling this method, Flutter will mark the state of the Widget at the underlying level, and then trigger the rebuild. For our example, even if you modify _counter, if you don’t call setState, the Flutter framework will not perceive the change in state, so there will be no change in the interface (you can try it yourself).

The following figure 3 is a schematic diagram of the code flow of the entire counter example. With this figure, you can connect the entire code flow of this example:

Figure 3 Schematic diagram of code flow

MyApp is the running instance of the Flutter application, and the entry point of the program is implemented by calling the runApp function in the main function. The home page of the application is MyHomePage, a StatefulWidget with the _MyHomePageState state. By calling the build method, _MyHomePageState creates the page view, including the navigation bar, text, and button, with the corresponding data configuration.

And when the button is clicked, its associated control function _incrementCounter will be triggered. In this function, by calling the setState method, the _counter property is updated, and the Flutter framework is notified that the state has changed. Subsequently, Flutter will call the build method again, rebuild the UI of _MyHomePageState with the new data configuration, and finally complete the page re-rendering.

Widget is just the “configuration information” of the view, the mapping of data, and it is “read-only”. For a StatefulWidget, when the data changes, we need to recreate the Widget to update the interface, which means that the creation and destruction of Widgets will be very frequent.

To optimize this mechanism, Flutter internally uses an intermediate layer to converge the upper-level UI configuration to the lower-level real rendering of the changes, in order to minimize the modification of the real rendering view and improve rendering efficiency, instead of destroying and rebuilding the entire rendering view tree when the upper-level UI configuration changes.

In this way, the Widget is just a lightweight data configuration storage structure, and its re-creation speed is very fast, so we can confidently rebuild any views that need to be updated without modifying the specific styles of each sub-Widget separately. I will provide detailed information about the rendering process of Widgets in the ninth article “Widget, the cornerstone of building Flutter interfaces”. I won’t go into details here.

Summary #

This is where we end today’s initial experience with the Flutter project. Next, let’s take a look back at the key points we covered.

First, we created a counter example using the Flutter standard template and analyzed the project structure of Flutter. We also explored the connection between Flutter projects and native Android and iOS projects, and learned how Flutter code runs on native systems.

Then, I walked you through the sample project code to understand the structure of a Flutter application and its pages. We also learned about the basics of building Flutter applications, namely Widgets, and the state management mechanism. We now know how Flutter pages are constructed, the differences between StatelessWidget and StatefulWidget, and how to use the member function setState of State to update the state in a data-driven manner and thus update the page.

Those who have experience in native Android and iOS framework development may be more accustomed to imperative UI programming style, where we manually create UI components and modify visual properties by calling their methods when necessary. In contrast, Flutter adopts a declarative UI design approach, where we only need to describe the current UI state (i.e., State). Flutter then takes care of the visual changes for different UI states at the underlying level.

Although imperative UI programming style may seem more intuitive, the benefit of declarative UI programming is that it allows us to delegate the complex view operation details to the framework. This not only improves efficiency but also enables us to focus on the overall structure and functionality of the application and pages.

Therefore, I really hope that you can adapt to this UI programming mindset shift.

Thought Exercise #

Finally, I would like to leave you with a thought exercise.

In the example project code, the construction of the Scaffold page elements is directly implemented within the build function of the _MyHomePageState class in an inline manner. What are the advantages of doing this?

If we wanted to encapsulate the construction of the Scaffold page elements into a new Widget class while achieving the same functionality, how should we handle it?

Please feel free to share your thoughts in the comments section. I will be waiting for you in the next article! Thank you for reading, and please share this article with more friends to read together.