Flutter simple cheatsheet

Manabie Tech-Product Blog
Manabie
Published in
7 min readFeb 22, 2020

At Manabie, we use Flutter in all of our apps: Learner App, Coach App, Tutor App, Teacher App. We started to develop our apps in June 2019, which has been almost 9 months until now.

Using Flutter is a fun experience for us: it is fast, possesses an easy learning curve, a hot reload to save us time, and a large support from the community,…

This article is some of our experiences for the last 9 months, including some simple cheats or solutions which we managed to find for each problem which we encountered, and some questions that my teammates usually asked me in the past.

We hope this article will help you guys. Now, here we go!

AutomaticKeepAliveClientMixin

  • When we develop our apps, our design usually has a Home screen with a multi Tab bar or Page view. The problem we have is that when we change pages, the state of the previous page would be lost. It was inconvenient.
  • AutomaticKeepAliveClientMixin is a mixin with convenience methods for clients of AutomaticKeepAlive — which allows subtrees to request to be kept alive in lazy list.
  • To use it, we just need to add AutomaticKeepAliveClientMixin in our state class of the widget you want to keep alive, then override wantKeepAlive variable, and finally add super.build(context) into your build method.
  • Snippet code:
class Page extends StatefulWidget {
final String title;
const Page({
Key key,
@required this.title,
}) : super(key: key);
@override
_PageState createState() => _PageState();
}
class _PageState extends State<Page> with AutomaticKeepAliveClientMixin {
int _value = 0;
@override
Widget build(BuildContext context) {
super.build(context); /// Remember to add this line!!!
return
Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(widget.title),
Text(_value.toString()),
RaisedButton(
child: Text('Increase'),
onPressed: () {
setState(() {
_value++;
});
},
),
],
),
);
}
@override
bool get wantKeepAlive => true;
}
Before (Left) and After (right) using AutomaticKeepAliveClientMixin

WillPopScope

  • Sometimes, when we are working on our app (request an API, calculate some logic,…) , we don’t want users to go back into the previous screen, before making sure our work is successful and its logic was done in the same place.
  • We can absolutely do that only by blocking the interaction of the Back button on the AppBar. But in Android there is another Back button in Navigation Bar. So how can we block this behavior? WillPopScope will help you in this case.
  • WillPopScope creates a widget that registers a callback to veto attempts by the user to dismiss the enclosing ModalRoute. WillPopScope will have onWillPop callback which verifies that it’s OK to call Navigator.pop.; if you return false, it will prevent the ModalRoute to pop and otherwise.
  • Snippet code:
import 'package:flutter/material.dart';class DemoWillPopScope extends StatefulWidget {
@override
_DemoWillPopScopeState createState() => _DemoWillPopScopeState();
}
class _DemoWillPopScopeState extends State<DemoWillPopScope> {
bool _allowPopBack = true;
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
return _allowPopBack;
}
,
child: Scaffold(
appBar: AppBar(title: Text('Demo WillPopScope')),
body: Center(
child: RaisedButton(
child: Text('Allow pop back: $_allowPopBack'),
onPressed: () {
setState(() {
_allowPopBack = !_allowPopBack;
});
},
),
),
),
);
}
}
WillPopScope Demo

RichText — TextSpan — WidgetSpan

  • RichText widget displays text that adopts multiple different styles. The text to display is described using a tree of TextSpan or WidgetSpan objects, each of which has an associated style used for that subtree.

Rich Text — TextSpan — WidgetSpan demo

  • RichText is very useful where our text requires diverse styles. For the above example, we have an italic text, a smiley Icon widget, a bold text and finally, our Manabie icon.
  • TextSpan also supports us with recognizer parameter, which will trigger our action: when tapped on the text it would be a TapGestureRecognizer or LongPressGestureRecognizer,…
  • Snippet code:
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:line_awesome_icons/line_awesome_icons.dart';
class DemoTextSpan extends StatelessWidget {
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey(); @override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
body: Center(
child: RichText(
textAlign: TextAlign.center,
text: TextSpan(
style: TextStyle(
color: Colors.black,
), /// This is the root style of the texts
children: [
TextSpan(
text: "Hello everyone ",
style: TextStyle(fontStyle: FontStyle.italic),
recognizer: TapGestureRecognizer()..onTap = onTapHello, /// Our callback for tap action
),
WidgetSpan(
child: Icon(LineAwesomeIcons.smile_o, size: 24.0),
alignment: PlaceholderAlignment.middle,
),
TextSpan(
text: " We are ",
style: TextStyle(fontWeight: FontWeight.bold),
),
WidgetSpan(
alignment: PlaceholderAlignment.middle,
child: GestureDetector(
onTap: () {
_scaffoldKey.currentState.showSnackBar(
SnackBar(
content: Text("Tap on Manabie!"),
),
);
},
child: Image.asset(
'assets/ic_manabie.png',
width: 24.0,
height: 24.0,
),
),
),
]),
),
),
);
}
void onTapHello() {
_scaffoldKey.currentState.showSnackBar(
SnackBar(
content: Text("Tap on hello!"),
),
);
}
}

Extension methods

  • When you’re using someone else’s API, it’s often impractical or impossible to change the API. But you might still want to add some functions. Then what should we do? Extension methods have come to the rescue!
  • Extension methods were introduced in Dart 2.7, with the keywords extension CustomName on ClassYouWantToExtensionOn
  • Snippet code:
import 'package:flutter/material.dart';extension Context on BuildContext {
void showCustomDialog(String text) {
showDialog(
context: this,
child: AlertDialog(title: Text(text)),
);
}
void push(Widget screen) {
Navigator.of(this).push(MaterialPageRoute(builder: (_) => screen));
}
void pop() {
Navigator.of(this).pop();
}
}
extension ExString on int {
Duration convertToDuration() {
return Duration(milliseconds: this);
}
}

And here is snippet code for using, remember to import your extension file:

import 'package:flutter/material.dart';
import 'package:flutter_simple_cheatsheet/home.dart';
import 'extension.dart';
class DemoExtension extends StatelessWidget { @override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Demo extension')),
body: Center(
child: RaisedButton(
child: Text('Click'),
onPressed: () {
context.push(HomeScreen());
context.showCustomDialog("My dialog");
context.pop();
Duration duration = 10000.convertToDuration();

},
),
),
);
}
}

It looks much shorter and we can reuse it anywhere we want!

IntrinsicWidth — IntrinsicHeight

  • IntrinsicWidth is a widget that sizes its child to the child’s intrinsic width.
  • In the image below, when we use a normal Column widget, our widget will fit to its size.
  • But when we use crossAxisAlignment: CrossAxisAlignment.stretch, our widget will be stretched to the maximum width of the parent widget.
  • Using IntrinsicWidth will make our widget only stretch to the maximum width of the biggest child.
Left image: Without CrossAxisAlignment.stretch and IntrinsicWidth; Center image: CrossAxisAlignment.stretch — without IntrinsicWidth; Right image: CrossAxisAlignment.stretch and IntrinsicWidth
  • Snippet code:
import 'package:flutter/material.dart';class DemoIntrinsic extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Demo Intrinsic'),
),
body: Center(
child: IntrinsicWidth(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
_buildContainer('a', Colors.red),
_buildContainer('This is a very looooooooong line!!!', Colors.blue),
_buildContainer('Manabie!', Colors.greenAccent),
_buildContainer('Some text', Colors.orange),
],
),
),
),
);
}
Widget _buildContainer(String text, Color color) {
return Container(
child: Text(
text,
textAlign: TextAlign.center,
),
height: 40.0,
color: color,
);
}
}
  • Note: These two widgets are very expensive, which add a speculative layout pass before the final layout phase. So please make sure if you really want to use it or only consider them as a last resort..

AnimatedCrossFade

Animated CrossFade Animation Example
  • As you can in the GIF above, we have fading animation when clicking on the calendar icon.
  • At first, we thought that would cost some effort to implement that animation and maintain its state. But Flutter has a widget to help us achieve this effect quite easily and economically — AnimatedCrossFade
AnimatedCrossFade(
duration: Duration(milliseconds: 500),
firstChild: FirstWidget(), /// Our first widget
secondChild: SecondWidget(), /// Our second widget
crossFadeState: _crossFadeState, /// CrossFadeState value
firstCurve: Curves.easeIn,
secondCurve: Curves.linear,
)
  • As you can see, we can control the animation Duration, the first widget and second widget to show up, depending on the crossFadeState variable.
  • crossFadeState is an enum, which only has CrossFadeState.showFirst and CrossFadeState.showSecond. It means that we will easily control which widget to be shown by changing the state of this variable.

The end

  • Thank you for your reading, we know that you guys might find it similar with the above ideas, but thanks again for reading our blog.
  • Flutter has a lot of widgets that we haven’t known yet. Widget of the week and The Boring Flutter Development Show series is a good series — we learnt many things from them.
  • If you like our simple cheatsheet, please don’t hesitate to give us a clap and, stay tuned for the next post from us — Manabie.

--

--