Review past

The Journey of Flutter from scratch: StatelessWidget

In the previous article, we introduced the features of StatelessWidget and its rendering in Flutter.

Let’s move on to its sister, StatefulWidget, commonly known as a StatefulWidget.

features

If you read my previous article, you’re probably already familiar with the StatelessWidget StatelessWidget. They are implemented by a blueprint with an immutable Element configuration that actually installs the individual StatelessElement onto the screen.

I like immutable things very much. Just like writing code, once I define an immutable variable, I don’t have to care about everything after it. Because of its immutable nature, it doesn’t have unexpected problems, and I just use it.

But a program with immutable configuration is not enough, we can’t write an application that draws once and then stops. Because once the data changes, immutable configuration is unlikely to help us refresh the UI to achieve the desired effect; The StatefulWidget takes care of these things easily.

StatefulWidget provides immutable configuration information and state objects that can be triggered over time; UI updates are made by listening for state changes.

To keep things simple, let’s pick an instance from Flutter_github.

When we click on one of the unread notification messages, we need to change its UI state to read style. Changing the UI based on state is a good way to do this. Let’s look at the implementation

class NotificationTabPage extends BasePage<_NotificationPageState> { const NotificationTabPage(); @override _NotificationPageState createBaseState() => _NotificationPageState(); } class _NotificationPageState extends BaseState<NotificationVM, NotificationTabPage> { @override NotificationVM createVM() => NotificationVM(context); @override Widget createContentWidget() { return RefreshIndicator( onRefresh: vm.handlerRefresh, child: Scrollbar( child: ListView.builder( itemCount: vm.notifications? .length ?? 0, itemBuilder: (BuildContext context, int index) { final NotificationModel item = vm.notifications[index]; return GestureDetector( onTap: () { vm.contentTap(index); }, child: Container( color: item.unread ? Color.white: color.fromargb (13, 0, 0, 0), padding: EdgeInsets. Only (left: 15.0, top: 10.0, right: 15.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[ Text( item.repository.fullName, style: TextStyle(fontWeight: fontWeight. Bold, fontSize: 16.0, color: item.unread? Colors. Color.fromargb (255, 102, 102, 102),),), Row(children: <Widget>[Padding(Padding: EdgeInsets. Only (top: 5.0)), child: Image.asset(vm.getTypeFlagSrc(item.subject.type), width: 18.0, height: 18.0,), Expanded(Child: Padding(Padding: EdgeInsets. Only (top: 5.0, left: 10.0), child: Text(item.subject.title, overflow: textoverflow. ellipsis, maxLines: 1, style: TextStyle(fontSize: 14.0, color: item.unread? Color.fromargb (255, 17, 17, 17) : Color.fromargb (255, 102, 102, 102),),),),),),),),), (Padding: EdgeInsets. Only (top: 10.0), child: Divider(height: 1.0, endIndent: 0.0, color: color.fromargb (255, 207, 216, 220),),); },),),); }}Copy the code

BasePage here is the base class in the MSVM architecture that inherits from StatefulWidget; The same goes for _NotificationPageState, which inherits from State

abstract class BasePage<S extends BaseState>
    extends StatefulWidget {
    ...
}
 
abstract class BaseState<VM extends BaseVM, T extends StatefulWidget>
    extends State implements VMSContract {
    ...
}Copy the code

There will be a special article about MSVM in the future. If you want to know more, you can look forward to it

Let’s look at the layout in the createContentWidget method and find the UI associated with the above situation in the ListView item.

The state of the item layout is determined by item.unread, which is true.

When the user onTap, a thread reading request will be sent to the server. When the request succeeds, the item.unread value of the corresponding position will be changed to false.

But if you do this, you’ll notice that the UI won’t refresh, because in StatefulWidget, if you want to change a value and update the UI simultaneously, you need to use the setState method.

  _markThreadRead(int index) async {
    try {
      Response response =
          await dio.patch('/notifications/threads/${_notifications[index].id}');
      if (response != null &&
          response.statusCode >= 200 &&
          response.statusCode < 300) {
        _notifications[index].unread = false;
        notifyStateChanged();
      }
    } on DioError catch (e) {
      Toast.show('makThreadRead error: ${e.message}', context);
    }
  }Copy the code

Here you wrap the setState method into the notifyStateChanged method. So now if you go back and look at the UI, you’ll see that the UI has been refreshed.

This is using the StatefulWidget to dynamically change the UI. When you look at the previous StatelessWidget, the differences are obvious.

Presents the principle of

As with StatelessWidgets, take a look at how StatefulWidget renders.

StatefulWidget also inherits from Widget, so it also has the createElement method inside it. Essentially, create the corresponding Element Tree with createElement, but create a StatefulElement. Then call the build method in the corresponding Widget Tree to get the corresponding blueprint.

But unlike StatelessWidget, it has another method

  @protected
  State createState();Copy the code

Create the corresponding State through createState. The StatefulWidget retains the features of the StatelessWidget that guarantee the immutability of final data, while non-final mutable data is managed through Stete.

Here is the schematic rendering of the previous StatelessWidget, and here is a comparison of the StatefulWidget.

In addition to Widget Tree and Element Tree, there are corresponding states that manage mutable data, such as item.unread.

Once item.unread changes and State is notified, State will ask the Widget Tree to refresh again in the next frame. Rebuild a Container

Because the Container is of the same type, it will be directly replaced with the updated item.unread, so the color of the corresponding Container will also change. The result is a refresh of the layout.

It is worth noting that State is attached to the Element Tree, so its life cycle is very long. Even if the NotificationTabPage in Widget Tree is removed and rebuilt, as long as the rebuilt type is consistent, If the location of the Widget Tree and Element Tree remains unchanged, the Widget will avoid rebuilding and will be marked as dirty. Its child widgets will then be rebuilt using the Build method, replacing the changed value in State.

If you want to listen for Widget changes, you can override the didUpdateWidget

  @override
  void didUpdateWidget(StatefulWidget oldWidget) {
    // TODO: implement didUpdateWidget
    super.didUpdateWidget(oldWidget);
  }Copy the code

In summary, StatefulWidget lets you keep track of data changes and update your app’s UI. However, when you get into Flutter, you will find that you are writing more statelesswidgets, because the StatefulWidget you need to use is basically implemented. We are more about the encapsulation of statelesswidgets, isn’t it interesting? We look forward to your joining us.

The code in this article comes from Flutter_Github, a Github client based on Flutter that supports both Android and IOS, including account passwords and authentication login. Dart language was used for development, and the project architecture was MSVM based on Model/State/ViewModel. Use Navigator to jump to the page; The network framework uses DIO. The project is being updated continuously, those who are interested can follow it.

Of course, if you want to learn about Android native, flutter_Github’s pure Android version AwesomeGithub is a good choice.

Next up

The Journey to Flutter from scratch: InheritWidget

If you like my article mode, or are interested in my next article, you can click my profile picture to follow, of course, you can also follow my wechat official account: [Android supply station]

Or scan the qr code below to establish effective communication with me and receive my update push faster and more accurately.