Flutter best practices

CodingWithTashi
8 min readJul 16, 2022

--

During your programming journey, you will build hell lots of applications irrespective of any technology that you use, and getting these things done is one thing but doing it the right way is what matters.

Flutter best practices

Hello guys, this is CodingWithTashi and in this shitty post, we are going to discuss some of the best practices that you can apply while writing flutter/dart code.

[Background story]

So the other day I was going through one of my old flutter application source codes (App link here, sorry source code is not open source 😊). It took me some time to go through each module as the code was messy and unstructured.

But in between, I realized how much I have improved on flutter over time. So I decided to refactor my application and here are the lists of mistakes that I have made but you can avoid them.

New comers: Those of you who don’t know me. I write shitty post on programming and share my flutter journey from time to time. So if you are someone who is interested, make sure to follow and subscribe to my medium.

Alright so, If you are in rush you can consider these bullet points and leave, otherwise, feel free to read the explanation with an example.

  • Use the const keyword whenever possible
  • Define constant with leading “k”
  • Use commas to intend your code
  • Follow naming convention
  • Use the right widget
  • Use private variable/method whenever possible
  • Proper use of setState()
  • Avoid deep trees instead create a separate widget
  • Avoid method implementation within widget instead create a separate method
  • Avoid using nullable unless it is nullable
  • Use cascade (..)
  • Use spread operator (…)
  • define theme/route in a separate file
  • avoid using hardcoded strings (internationalized your app)
  • avoid using hardcoded style, decoration, etc.
  • Use relative import over package import within your app
  • Use a stateless widget whenever it is possible
  • Use flutter lint

Note: Keep in mind that there is nothing wrong with your application code. Following example explained the best and right way to implment the same logic.

Using the const keyword whenever possible

Using a const keyword is always a good practice since it will avoid rebuilding the widget.

❌ Don’t

Column(
children: [
Text('Text One'),
Text('Text Two'),
],
)

✅ Do’s

Column(
children: const [
Text('Text One'),
Text('Text Two'),
],
)

Use commas to intend your code

You might want to add comma as many as possible to intend your application code properly.

❌ Don’t

@override
Widget build(BuildContext context) {
return const Scaffold(body: Center(child: Text('Text One')));
}

✅ Do’s

@override
Widget build(BuildContext context) {
return const Scaffold(
body: Center(
child: Text(
'Text One',
),
),

);
}

Define constant with leading “k”

[Updated]

I had to update this point since defining constant with leading k is misleading.

  • Flutter style guide recommends prefixing global constants with ‘k’ read here for more
  • but Dart the Effective Dart style guide recommends not using prefix read here for more.
  • Here is the git issue for the same.

Follow naming convention

In dart, there are things you need to keep in mind while naming such as

  1. file names always use camel case with a small case. eg. home_page.dart, file_manager.dart, etc
  2. class names start with cap. eg.
  3. method name start with small
  4. Use underscore if the field, method, or class is private

❌ Don’t

1. File name
Home_Page.dart
File_manager.dart
2. Class name
class myApp
class myapp
3. method name
void getdata() {}
void GetData() {}
4. Private variable
String privateVariable

✅ Do’s

1. File name
home_page.dart
file_manager.dart
2. Class name
class MyApp
3. method name
void getData() {}
4. Private variable
String _privateVariable

Use the right widget

We all know flutter is all about widgets, but using the right widgets sometimes matters. for example, using a container widget just to get padding does not make any sense. use the Padding widget instead.

❌ Don’t

Container(
padding: const EdgeInsets.all(8),
child: const Text("Hello"),
),
Container(height: 10,),

✅ Do’s

const Padding(
padding: EdgeInsets.all(8),
child: Text("Hello"),
),
const SizedBox(height: 10,)
Flutter best practices

Use private variable/method whenever possible

Unless required, use a private keyword whenever possible.

❌ Don’t

class Student {
String name;
String rollNo;

Student({
required this.name,
required this.rollNo,
});
}

✅ Do’s

class Student{
String _name;
String _roolNo;

Student({
required String name,
required String roolNo,
}) : _name = name,
_roolNo = roolNo;
}

Proper use of setState()

When you are not using any state management tool, you might need to rebuild your widget tree with setState but make sure you are using it the right way.

You don’t want want to rebuild the whole widget just because you want to update a single text field instead create a separate widget and rebuild that new widget only. It is also known as lifting the state up.

❌ Don’t

Column(
children: [
//long list of widget here
Text(
_textValue
),
ElevatedButton(
onPressed: (){
setState((){
_textValue="new value";
});
},
child: Text(
'Update',
),),
//long list of widget here
],
)

✅ Do’s

Column(
children: [
//long list of widget here
NewWidgetWithTextAndButton(),
//long list of widget here
],
)

Avoid deep trees instead create a separate widget

Trust me, I have faced this issue many times. You don’t want to keep on scrolling your IDE with a thousand lines of codes. Try creating a separate widget instead. It will look clean and easy to refactor.

❌ Don’t

Column(
children: [
Container(
//some lengthy code here
),
Container(
//some another lengthy code
),
//some another lengthy code
],
)

✅ Do’s

Column(
children: [
FirstLengthyCodeWidget(),
SecondLengthyCodeWidget(),
//another lengthy code widget etc
],
)

Avoid method implementation within widget instead create a separate method.

Do you have 50 lines of code within onPressed() on some button? Not a good practice. Wrap them in a method outside your widget.

❌ Don’t

@override
Widget build(BuildContext context) {
return Scaffold(
body: ElevatedButton(
onPressed: () {
//long lengthy code here
},
child: const Text('Do some task'),
),
);
}

✅ Do’s

@override
Widget build(BuildContext context) {
return Scaffold(
body: ElevatedButton(
onPressed: _callTheListener,
child: const Text('Do some task'),
),
);
}
void _callTheListener(){
// your code here
}

Avoid using nullable unless you know it is nullable

One good thing about dart is that unlike java it supports null safety, In my old projects, I was getting lots of null exceptions since I wasn't using null safety properly in my applications.

So avoid using nullable unless necessary.

❌ Don’t

TextEditingController? textEditingController;@override
initState() {
super.initState();
textEditingController = TextEditingController();
}

✅ Do’s

late TextEditingController textEditingController;
@override
initState() {
super.initState();
textEditingController = TextEditingController();
}

Use cascade (..)

If you are just starting with flutter, You might have not used this operator but it is very useful when you want to perform some task on the same object.

❌ Don’t

var paint = Paint();
paint.color = Colors.black;
paint.strokeCap = StrokeCap.round;
paint.strokeWidth = 5.0;

✅ Do’s

var paint = Paint()
..color = Colors.black
..strokeCap = StrokeCap.round
..strokeWidth = 5.0;
FLutter best practices

Use spread operator (…)

This is another beautiful operator that dart has to offer. You can simply use this operator to perform many tasks such as if-else, join the list, etc.

❌ Don’t

@override
Widget build(BuildContext context) {
bool isTrue = true;
return Scaffold(
body: Column(
children: [
isTrue ? const Text('Text One') : Container(),
isTrue ? const Text('Text Two') : Container(),
isTrue ? const Text('Text Three') : Container(),
],
),
);
}

✅ Do’s

@override
Widget build(BuildContext context) {
bool isTrue = true;
return Scaffold(
body: Column(
children: [
if(isTrue)...[
const Text('Text One'),
const Text('Text Two'),
const Text('Text Three')
]

],
),
);
}

Define route/theme in a separate file

You might want to define the route and theme in a separate file because you might end up requiring multiple themes in your application and many routes in your app.

❌ Don’t

@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
textTheme: TextTheme(),
primaryColor: Colors.green,
//long list of other properties
),
home: const HomePage(),
);
}

✅ Do’s

@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: appTheme,
home: const HomePage(),
);
}
//In new file add your theme var appTheme = ThemeData(
primarySwatch: Colors.blue,
textTheme: const TextTheme(),
primaryColor: Colors.green,
//long list of other properties
);

Avoid using hardcoded strings (internationalized your app)

This might not look helpful but trust me when your application starts to grow, You will end up doing a lot of searches and replacing.

❌ Don’t

Text(
'Text One',
),

✅ Do’s

Text(
AppLocalizations.of(context)!.textOne,
),

Avoid using hardcoded style, decoration, etc.

If you are using a hardcoded style, decoration, etc in your application and later on if you decided to change those styles. You will be fixing them one by one.

❌ Don’t

Column(
children: const [
Text(
'Text One',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.normal,
),
),
Text(
'Text Two',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.normal,
),
),
],
)

✅ Do’s

Column(
children: [
Text(
'Text One',
style: Theme.of(context).textTheme.subtitle1,
),
Text(
'Text Two',
style: Theme.of(context).textTheme.subtitle1,
),
],
),

Use relative import over package import within your app

When you import a path in your file if you want to import an application class or file, It is recommended to use relative import over package import.

❌ Don’t

import 'package:flutter/material.dart';
import 'package:test_project/student.dart';

✅ Do’s

import 'package:flutter/material.dart';
import 'model/student.dart';

Use a stateless widget whenever it is possible

Avoid using the stateful widgets as much as you can, that way you don’t need to build the widget over again and again with setState.

❌ Don’t

class TestWidget extends StatefulWidget {
const TestWidget({Key? key}) : super(key: key);

@override
State<TestWidget> createState() => _TestWidgetState();
}

class _TestWidgetState extends State<TestWidget> {
@override
Widget build(BuildContext context) {
//stateless content
return Image.asset('assets/cat_walking.png');
}
}

✅ Do’s

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

@override
Widget build(BuildContext context) {
//stateless content
return Image.asset('assets/cat_walking.png');
}
}

Use records to return multiple value [dart 3 feature]

❌ Don’t

String getPersonName(Map<String, dynamic> json) {
return json['name'];
}
String getPersonAge(Map<String, dynamic> json) {
return json['age'];
}

✅ Do’s

(String, int) getPersonDetail(Map<String, dynamic> json) {
return (
json['name'],
json['age'],
);
}

Use enhance switch [dart 3 feature]

❌ Don’t

enum Type {
kid,
teen,
adult;

int get discount {
switch (this) {
case kid:
return 30;
case teen:
return 20;
case adult:
return 5;
}
}
}
void main() {
print(Type.kid.discount);
}

✅ Do’s

enum Type {
kid,
teen,
adult;

int get discount => switch (this) {
kid => 30,
teen => 20,
adult => 5,
};
}
void main() {
print(Type.kid.discount);
}

Use lint/flutter lint

These days, flutter provide flutter lint out of the box where you can define sets of rule for your code. Make sure to use it in your application.

✅ Do’s

flutter_lints: ^2.0.0
Flutter best practices

Phew!!, If you made till here I think you have done a great job learning all these best practices. Now I should go and cook my breakfast. I can smell my pancake burning smell already😂.

If any of my shitty posts help you in any way, you can consider buying me a cup of coffee. I will personally thank you 🙏.

buymeacoffee.com/codingwithtashi

Thanks, guys, That’s all for now, Make sure to give a clap 👏 and leave some engagement. If there is any correction or feedback feel free to leave a comment as well.

Check out similar posts from CodingWithTashi. Connect me on Twitter or Linkedin

--

--