March is coming and with that, the next big Flutter event is also close. Scheduled on March 3rd of this year some big rumors have already surfaced up, like Flutter for web might get a stable release and many more. It's no better time to learn Flutter!
What would be better than to start than building our first app? So let's get started right away!
Development Environment Setup
For this tutorial series, I am using Visual Studio Code as my Editor. You can use any editor of your choice. Make sure Flutter SDK is installed on your machine. For detailed installation instructions refer to my previous post. In addition, you must have Android Studio installed to run the app on Emulator or any Android device. If you are using macOS you can also configure X-Code to run your Flutter apps on an iOS simulator or actual iOS devices.
Our Demo App
Here, we will be building a demo single page application to get quickly familiar with Flutter and some of its widgets. Our Demo Application will look like as below,
This is a very simple single page/screen app that we will build to display some text on the App Bar and a button. On the button click, we will change the background color.
Creating a new App
To create a new app navigate to your desired location on your terminal and use the command below,
flutter create your_project_name
Alternatively, you can create a new Flutter project from your Android Studio or VS Code command palette.
The Project Directory Structure
Let's have a quick look at our project directory structure before starting our first app.
>android
>build
>ios
>lib
-> main.dart
>test
>.gitignore
>pubspec.lock
>pubspec.yaml
>README.md
The 'android' Folder
In this folder, all the Project files for the android app reside. You can make changes, add the required permissions and native android code here.
The 'build' Folder
This folder holds all the built and generated outputs like app bundles, apk's and other relevant files and folders.
The 'ios' Folder
In this folder, all the Project files for the iOS app reside. Similar to the android folder you can add required permissions and add native iOS code here.
The 'lib' folder
This is the folder where all the magic happens. In your lib folder, you can already see that you have a main.dart
file. All your dart
code is written in this folder which compiles to native platform code (native android and iOS) during the build process.
The 'test' folder
In this folder, you can write unit tests for your flutter app.
The '.gitignore' file
I assume you are pretty familiar with this file if you are using GIT as your version control system for your projects. This holds all the files and folder names that need not be added to the GIT.
The 'pubspec.lock' and 'pubspec.yaml' files
These files hold all the required packages names, their versions, link to your assets, dependencies, your app name, app version, dependencies of your app, etc.
The 'README' markdown file
This is a markdown file that contains all the basic information and description about the app.
With this file structure out of the way let's get started with our first demo application.
Running apps on emulators/real devices
To run your app create or open an existing android emulator or iOS simulator. Open your lib
folder and main.dart
file and press F5
to run your app. If you have flutter and dart extension installed in your VS code editor then hover over your void main()
function in your main.dart
file and you will see a Run|Debug
option.
On running the app you will see a basic flutter app that is created automatically. main.dart file is the entry point of your flutter application. The execution of any Flutter application starts from the void main() {...}
function in this file. Now let's clear everything and start our app from scratch.
Building the app
With your main.dart file cleared. Let's start building the app right away.
Importing Packages
Before anything else, we need to import the required packages. In this app we require the material package offered by flutter. All the widgets and functionality is built on top of this package. We can import this package using the import
statement as below,
import 'package:flutter/material.dart';
If your app requires any other packages the import statement will be similar.
Creating a Widget
Everything in a Flutter app is a Widget! So, whatever we need to display has to be a widget. There are two types of widgets that are provided by the material.dart
package on top of which we build our own custom widgets. Those two are Stateless Widgets and Stateful Widgets.
We will discuss state
, stateful widgets
, and stateless widgets
in much detail and depth in my next upcoming blogs in this series.
The IDE or Editor which you are using will help you build your own custom widgets if you have Flutter and Dart plugin installed. Simply type stateless
or stateful
and your IDE will give suggestions accordingly.
Here, we will create a stateless widget with the name MyApp. The widget can have any name of your choice.
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container();
}
}
As you can already see that we are extending the StatelessWidget
offered by the material
package to create our own custom stateless widget. @override
is a decorator which is also provided by the material
package.
Widget is an abstract class and we are overriding the build method. This method requires a context which is of type BuildContext which is provided by Flutter. Inside this method, we return the Widget that we need to build/display on the screen.
So here we will return a MaterialApp which is also a built-in widget provided by Flutter's material.dart
package. This widget accepts multiple arguments in its constructor. You can find more details by hovering over the Material App
or Command + Click
/ Ctrl+Click
on the MaterialApp widget. Official documentation can be found here.
We need to define 3 parameters to our MaterialApp, i.e, title
, theme
, and home
. title
simply accepts a string. I will provide it with First App Demo
as value. theme
expects a ThemeData
datatype value which is a built-in type in the Flutter material package. So, initialize it with,
ThemeData(primarySwatch: Colors.amber,),
ThemeData
accepts multiple values to configure app Default theme, primarySwatch
is a required parameter that accepts a value of type MaterialColor
. This is also a built-in type and provides a color palette to Flutter to build our app theme. I have provided Colors.amber
which is a nice shade of orange and yellow.
Now, we need to configure home
which accepts a Widget
type. The Widget that you provide here will be rendered onto the screen. For now, we will initialize it with an empty container like this, home: Container(),
. We will replace this soon with our custom widget. Our widget looks like this now,
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'First App Demo',
theme: ThemeData(
primarySwatch: Colors.amber,
),
home: Container(),
);
}
}
The main(){}
function
To run our app we need the main function. The main function can be written using general syntax (){...}
or by defining it as an arrow function ()=>
. Both the syntaxes are as below,
void main() {
runApp(MyApp());
}
or,
void main() =>runApp(MyApp());
Both, the syntaxes will work fine. The shorthand arrow function is more elegant though if we need to return a single value which is the case here. Now, run your app with F5
or Run|Debug
and you will see a blank screen.
Creating a StatefulWidget
Instead of displaying an empty container that will render nothing but a black screen in the end let's build something more classy!
To create a StatefulWidget
use your IDE or Editor auto-complete feature by simply typing stateful and press tab
. Here, I am creating a Stateful widget with the name DummyWidget
as below,
class DummyWidget extends StatefulWidget {
@override
_DummyWidgetState createState() => _DummyWidgetState();
}
class _DummyWidgetState extends State<DummyWidget> {
@override
Widget build(BuildContext context) {
return Container();
}
}
You can clearly see that, StatefulWidget
is a bit different than StatelessWidget
. For now, all I will say is that Stateful Widgets can react to changes in data internally and rebuild themselves to reflect the changes whereas Stateless widgets don't rebuild themselves if data changes within them.
I will discuss this in much detail in my upcoming posts.
We need some type of internal data so that we can understand Stateful Widgets. Here, I will take a boolean
value named _isGreen
which I will initialize false
by default. I am using _
in front of the variable that makes it a private variable in Dart and accessible only inside this class.
Now, instead of returning an empty container again from the Widget build(BuildContext context) {...}
method, we will return something interesting!
We will return a Scaffold
which is also a widget provided by the material
package. Note that every time you want to render a new/different screen you need to provide a Scaffold
. Scaffold accepts several parameters in its constructor.
Here, we will provide it with appBar
and body
to see some content on the screen. The DummyWidget will look like this,
class _DummyWidgetState extends State<DummyWidget> {
bool _isGreen = false;
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: _isGreen ? Colors.green : Colors.red,
appBar: AppBar(
title: Text('Your First App'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
FlatButton(
onPressed: () {
setState(() {
_isGreen = !_isGreen;
});
},
child: Text(_isGreen ? 'TURN RED' : 'TURN GREEN'),
),
],
),
),
);
}
}
Let's visualize what is happening in our Stateful DummyWidget. We have a private variable of type bool
named _isGreen
which is false
by default. Instead of returning a container, we are returning Scaffold
widget and we have provided it with 2 parameters namely appBar
and body
.
The appBar
is accepts a value of type PreferredSizeWidget
which in this case is AppBar()
. AppBar()
also accepts many parameters for configuration but we have used only one here to configure the title
that is displayed on the AppBar
. The title
accepts a type Widget
so we are using inbuilt Text()
widget.
The body
also accepts a type Widget
so here we are using built-in Column()
widget. Column extends from top to bottom of the screen by default and wraps itself around its child widgets. Column can hold multiple widgets as its children.
Here, we have a single child of Column()
widget that is a FlatButton()
which is also a widget provided by the material
package. Flatbutton()
has an onPressed
and a child
parameter. onPressed
accepts a function that can be anonymous or a named one. The content that you write inside this function defines what will happen when this button is pressed.
Inside this anonymous function, we are using setState(() {...});
which triggers the Widget rebuild. If we change some internal value as in our case we change the boolean value _isGreen
inside the set state. On every button click this value changes and triggers the build process of this stateful widget.
In the end we provide Text('...')
widget as a child to the FlatButton(...)
. This specifies what is displayed inside the button.
Lastly, in our stateless Widget MyApp instead of returning an empty container we return our custom stateful widget DummyWidget. With this in your running application, you can already see your custom widget on your screen. Now, our finished code in main.dart
file looks as below,
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'First App Demo',
theme: ThemeData(
primarySwatch: Colors.amber,
),
home: DummyWidget(),
);
}
}
class DummyWidget extends StatefulWidget {
@override
_DummyWidgetState createState() => _DummyWidgetState();
}
class _DummyWidgetState extends State<DummyWidget> {
bool _isGreen = false;
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: _isGreen ? Colors.green : Colors.red,
appBar: AppBar(
title: Text('Your First App'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
FlatButton(
onPressed: () {
setState(() {
_isGreen = !_isGreen;
});
},
child: Text(_isGreen ? 'TURN RED' : 'TURN GREEN'),
),
],
),
),
);
}
}
Conclusion
Yupp! I know that feeling. You have built your first app using Flutter and so little code. It's time to play with that button and change your app colors. Go ahead! and ram that button😅.
I hope you liked this post. If you have reached this far then any reaction 👍🍺🎉👏 on the post would be awesome. Stay tuned for the next upcoming post in this series where we will discuss some Dart fundamentals in detail. Thanks for reading it till the end. Happy Coding👨🏽💻 Fellas!