The Ultimate Guide to App Development with Flutter

A complete and comprehensive guide to learning Flutter with explanations, tips, resources, and examples for Dart, Flutter, Firebase, State Management and more.

Table of Contents

  1. Introduction
  2. Getting Started
  3. Learning Dart
    • Variables
    • Functions
    • Conditionals
    • Loops
    • Classes, Objects, and Constructors
    • More Resources
  4. Learning Flutter UI
    • Installation
    • Widgets
    • Layout
    • Formatting
    • Stateless Widgets
    • Stateful Widgets
    • Null Safety
  5. Learning Firebase
  6. Connecting Firebase wth Flutter
    • StreamBuilder
    • FutureBuilder
  7. State Management
  8. Best Practices
    • Folder Structure
    • Separate Business Logic from Frontend
    • Abstract as much as possible (make more widgets)
    • Testing
  9. Helpful Resources

Introduction

Flutter is a powerful and intuitive framework for building beautiful, cross-platform mobile applications that uses the Dart programming language.

This essentially means that Flutter can be used to write one codebase for an app that runs natively on both iOS and Android.

With all the hype around Flutter and mobile app development, learning Flutter is both a valuable skill and a gratifying endeavor in its own right. However, the path to learning Flutter is a little unclear due to how new the language is.

  • the language is constantly being updated (to the point where tutorials from just a few months ago are out of date)
  • there are a lack of freely available, well thought out and comprehensive courses or books compared to some other more established frameworks and languages like python

This guide compiles tutorials, tips, examples, and resources to help make the learning process for Flutter much easier. You can be a complete beginner, an intermediate or even advanced programmer to use this guide. I hope you find it helpful!

Getting Started

Before we get started with dart and flutter, we first need to set up our programming environment, which is what we will be using to code flutter apps.

The two main IDEs that provide the most features for Dart and Flutter are Visual Studio Code (VS Code) and Android Studio. Choosing either one is up to you, but I do have a slight preference to VS Code because it looks sick...

Vim can also be used if you have coc or native lsp and install the dart and flutter extensions.

Choosing the right IDE is essential to getting all the features that the Dart programming language provides us with. Once you have your IDE / Text Editor, make sure you install the Dart extension and the Flutter extension. With these extensions, our IDE / Text Editor will perform extremely detailed error checking, type checking, null safety checks, and formatting to make our lives as developers a lot easier.

Once you have your environment set up, let's move on!

Learning Dart

Dart is a language developed by Google that is the backbone to the flutter framework. It is the language you will be using when you code up apps with the Flutter framework.

If you have never coded before or have less experience with programming, I recommend you take a look at this excellent tutorial from Mike Dane on YouTube (PS don't sit through the whole thing all at once! Spend some time thinking about programming concepts in your subconscious mind while you take breaks or do other things).

If you have experience with languages like javavscript or java, here are the basics of dart.

Outline:

Variables

Variables in dart are type-checked, which means that every variable must be declared with a specific type, and that type must match with what the variable is assigned throughout your programs.

Here are some basic types and examples:

String foo = 'foo';
int bar = 0;
double foobar= 12.454;
bool isCool = true;
List<String> = ['foo', 'bar'];

Dictionaries (which map keys to values) are specificed as the 'Map' type in dart. You have to specify the key type and the value type, like as follows.

Map<String, int> grades = {
  'John': 99,
  'Doe': 30,
}

You will get an error if you assign an incompatible type to the same variable.

String foo = 'foo';
foo = 2; // ERROR

You can use 'var' and 'dynamic' to make a variable type dynamic, but it is usually not a good idea to do this, as it could end up in frustrating errors down the line.

Additionally, dart has a unique 'final' and 'const' operator that can be used for declaring variables. 'final' is generally used to declare a variable that won't change once it's declared. For example, if a user types in their name and we save it to a variable, we know that variable (their name) won't change, so we can initialize / declare it like so:

final String name;

The 'const' keyword is a little more of a specific use case - it makes the variable constant from compile-time only. It will be useful later down the line for the Flutter framework, but for now, don't worry about 'const.'

Functions

Functions are declared by specifying the return type, the name of the function, and the parameters within paranetheses. Void is used to specify the return type of nothing is returned.

// doesn't return anything but still executes some code
void main() {
  print('hello world');
}

// prints 'hello' but also returns the string 'complete'
String hello(int reps) {
  for (int i = 0; i < reps; i++) {
    print('hello');
  }
  return 'complete';
}

// returns a list of strings (List<String>)
List<String> people() {
  return ['John', 'Doe'];
}

Asynchronous functions are functions that can execute different commands at the same time - asynchronously.

An example of how this would be useful is in calling APIs (basically, trying to retrieve some sort of useful information or data that was programmed by someone else, from the web). If our function calls an API and assigns a variable to the API's response, but our entire App is waiting for that function to finish executing in order to do something, then it isn't very efficient. If we make this function asynchronous, the function calling the API can then execute at the same time that the App allows other functions to execute, or while the App does something else.

Within an asynchronous function, if we ever need our function to wait for some line of code to finish before we continue, we simply precede the code with the keyword, 'await'.

For asynchronous functions in dart, add the 'async' keyword between the parentheses and the curly braces, and enclose the return type in 'Future<[return type]>'.

Future<String> retrieveData() async {
  response = await someAPICall(); // assuming the api call returns a string
  return response;
}

Conditionals

If statements are simply written as follows:

bool someCondition = true;

if (someCondition) {
  print('someCondition is true');
} else {
  print('someCondition is false');
}

Loops

For loops are very important in all programming languages, and there are a few ways to implement them in dart.

List words = ['hello', 'world', '!'];

// 1st way
// declare an int i, increment it by 1 until it is no longer 
// less than words.length (3 in this case)
for (int i = 0; i < words.length; i++) {
  print(words[i]);
} 

// 2nd way
// for each element in word, dart will take that element (in this case, a string, word)
// and will allow you to execute code using that element (here, we just print it out)
// the rocket notation (=>) allows us to write only a single statement to execute
// on the right side. otherwise, we would do (word) { print('hey!'); print(word); }
words.forEach((word) => print(word));

// 3rd way
// very similar to the 2nd way but a different syntax
for (String word in words) {
  print(word);
}

Pretty cool!

Classes, Objects, and Constructors

Classes can be created and used like this. Notice how the type of the object that is instantiated is declared, and how the object is instantiated.

class Car {
  String name;
  int price;
  bool isMadeByElonMusk;
}

void main() {
  Car tesla = Car();
  tesla.name = 'Model S';
  tesla.price = 50000;
  tesla.isMadeByElonMusk = true;
}

Here's an example of how constructors and methods work.

class Car {
  Car(String name, int price, bool isMadeByElonMusk) {
    this.name = name;
    this.price = price;
    this.isMadeByElonMusk = isMadeByElonMusk;
  }
  String name;
  int price;
  bool isMadeByElonMusk;
  
  bool isExpensive() {
    if (this.price > 30000) {
      return true;
    } else {
      return false;
    }
  }
}

void main() {
  // instantiate the class by using its constructor
  Car tesla = Car('Model S', 50000, true);
  // returns true by using the Car class's method, isExpensive, because tesla.price = 50,000
  bool isCarExpensive = tesla.isExpensive();
}

More resources

As always, make sure you review these concepts often to get familiar with them. Here are some more resources to help you cement these into your brain that I found very useful when I was learning.

Learning Flutter UI

Now that you know some of the basics of the dart programming language, let's take a look at the Flutter framework - first, we'll install a programing environment for Flutter.

Installation

The installation process can be a bit tricky for some users depending on the OS, but it isn't too bad. Follow these resources to install Flutter and the necessary tools for your OS (in addition to Flutter, you will also need an emulator / virtual phone in order to test your apps).

Windows

MacOS

Linux

Once finished, run this command in the terminal to make sure your environment is all ready to go.

$ flutter doctor

Create a flutter project with the following command.

$ flutter create <project_name>

The folder structure will look something like this. We will be putting all of our code in the 'lib' folder, and I'll explain the other folders later in the guide. For now, just follow along with the code in the guide and don't worry about project setup just yet.

Good job! Now that we have our environment set up, let's take a look at how apps are laid out in the Flutter framework.

Widgets

Flutter apps are built using things called Widgets. If you are familiar with a frontend javascript framework, these are akin to components, but many come already built by the framework. Widgets are also quite similar to HTML elements like 'p' (for paragraph), 'h1' (for header 1), etc.

Widgets are essentially the basic elements or building blocks of an app that Flutter has created for us. They are instantiated with specific properties or parameters that Flutter is expecting from you. For example, to display text on the app screen, we use a widget called the Text widget, comparable to the html 'p' element, that is instantiated by passing in a string. Here's what it looks like, in code and on an app.

// displays the text on the app screen
Text('Some string here');

There's also a prebuilt button widget from the Flutter library called the ElevatedButton (just a Material theme button) which takes in an onPressed property (the code to be executed after the button is pressed) and a child property (the Text widget that displays the text of the button). Another one is the TextField, which handles input text.

Layout

Widgets are also used for things more complicated than just displaying text or pressing buttons. The way Flutter lays out things in the app is also done through widgets. For example, the Container widget, which is akin to the 'div' in html, will give us the ability to wrap another child widget in a container, in order to add padding, margins, colors, or something else. The inner widget is usually called the 'child' widget, and the container would be the 'parent' widget of the 'child' widget. Makes sense, right?

Container(
  child: Text('hello!' )
),

Some more important layout widgets are the Row and Column widgets. These widgets allow you to stack widgets horizontally or vertically on the screen. They are instantiated by passing in a list of children widgets. Here's how they work.

Row(
  children: [
    // in the app, child widgets of a row are laid out left to right like so
    Text('left text'),
    Text('middle text'),
    Text('right text'),
  ],
)

Column(
  children: [
    // child widgets of a column are laid out top to bottom like so
    Text('top text'),
    Text('middle text'),
    Text('bottom text'),
  ],
)

Left: Row, Right: Column

Some layout widgets are wrapped around every other widget we put onto the screen. For example, the Scaffold widget is usually used to lay out or 'scaffold' the screen for us, and it is used like this:

Scaffold(
  body: Container(
    child: Text('hi!'),
  ),
)

Left: with Scaffold, Right: without Scaffold

Another useful widget is the ListView.builder widget. The ListView.builder widget takes in two main arguments - the itemCount (how many list items to build), and the itemBuilder (which will return what is actually built). Here's what it looks like.

List<String> people = ['John', 'Doe', 'Jane'];

ListView.builder(
  itemCount: people.length, // 3
  // index is the current index that the builder is iterating on. think of it like the 
  // 'i' in the for loop,  for (int i = 0; i < whatever; i++) 
  itemBuilder: (context, index) {
    return Container(
      child: Text(people[index]),
    );
  },
)

We will see later how these look in screenshots.

Properties / Parameters

Each widget built by Flutter can be passed a number of properties or parameters. As we saw earlier, the Container widget takes in a 'child' property, and it can also take in a 'color' property to define the background color of the Container.

Each widget will have a number of parameters specific to that widget that you can learn about by reading the Flutter Documentation or by using the IntelliSense of your IDE / Text Editor. For example, in VS Code, you can press ctrl+space or hover after typing in a Widget to see what properties it can use.

Usually, you can also pass in all of your styles to the widget through the parameter.

Many of these parameters only accept very specific types or objects. The 'child' property of the Container widget will only accept another Flutter widget. The 'color' property will only accept objects predefined by Flutter (like Colors.black, Colors.blue, etc) or objects instantiated in a certain way (Color(0xFFFFFFFF), one way to do it using hex codes).

In the Text widget, we can style the text by passing in a 'TextStyle' object instantiated with our styles, passed into the 'style' property of the Text widget.

Text(
  'text to display',
  style: TextStyle(
    // font color
    color: Colors.purple,
    // font size
    fontSize: 16.0,
    // font weight
    fontWeight: FontWeight.bold,
  ),
)

For styling in a Container widget, we use the 'decoration' property and pass in a 'BoxDecoration' object that is instantiated with our styles.

Container(
  // styling the container
  decoration: BoxDecoration(
    // you can define the background color in this object instead
    color: Colors.blue,
    // border radius - valid arguments must be of class BorderRadius
    borderRadius: BorderRadius.circular(20.0),
  ),
  height: 50.0,
  width: 50.0,
  // margin of the container - argument must be of class EdgeInsets
  margin: EdgeInsets.all(8.0),
  // child element (using the Center widget centers the Text widget)
  child: Center(
    Text('hello!')
  ),
)

In Column widgets, you might need to vertically align your objects to the center of the page. Here's how you could do that using the Column widget's 'mainAxisAlignment' property (main axis of the column is vertical). You can also align text horizontally in a column widget using the 'crossAxisAlignment' property.

Column(
  // argument passed in must use the MainAxisAlignment object 
  // can you start to see the practices and conventions Flutter everywhere?
  mainAxisAlignment: MainAxisAlignment.center,
  children: [
    Text('top text'),
    Text('center text'), 
    Text('bottom text'),
  ],
)

Left: without MainAxisAlignment.center, Right: with MainAxisAlignment.center (as in the code example above)

Other properties of Column include crossAxisAlignment, mainAxisSize, and more. Chances are, if you feel like you need to do something to style your widget, you just need to Google the properties of that widget, or Google how to accomplish what you need to find which property to use.

The amount of properties and classes you need might seem a bit daunting to learn about, but over time it will become intuitive for you (plus, Google is your best friend)!

Formatting

Now, you might be wondering, what the heck are all these commas and new lines everywhere? The reason I've laid out the code like this is because of how your IDE will format your code for you. It does this by detecting trialing commas and adding corresponding new lines.

Adhering to the formatter will make your code much more readable both for yourself and for others. Here's a simple example.


// weird code you might write totally without a formatter
// not very good, is it?
Column(children:[
  Container
  (child: Text
  (
    'hi!'
  )),
  Text(
    'hi'
  )
]
)

// code you might write with the formatter, but without adhering to the formatting guidelines
Column(children: [
  Container(color: Color(0xFFFFFF), child: Text('hey there'), margin: EdgeInsets.all(5.0), padding: EdgeInsets.all(5.0)),
  Text('hi')])

// code you write with the formatter, that adheres to the formatter
Column(
  children: [
    Container(
      color: Color(0xFFFFFF),
      child: Text('hey there'),
      margin: EdgeInsets.all(5.0),// add a trailing comma to the last parameter (margin)
    ), // add a trailing comma to the Widget
    Text('hi'), // add a trailing comma to the last child of the Column
  ], // add a trialing comma to the children parameter
)

Would you agree with me in saying that the last example is the easiest to read and the easiest to code with (disregarding the comments)?

Simply just add a trailing comma to your widgets and their parameters, hit save, and the formatter will do the rest for you. Over time, you'll get better and better at it.

Stateless Widgets

Stateless widgets are essentially widgets that don't change - they are static. One example of a stateless widget would be a page that displays the names of the states in the US in a list. Let's take a look at a more simple example by creating a stateless widget that simply returns a white container. Here's the syntax for defining a stateless widget.

class ListOfStates extends StatelessWidget {
  // this is the constructor, but don't worry about it right now
  const ListOfStates({Key? key}) : super(key: key);

  // @override is good practice to tell us that the following method (in this case,
  // the build method) is being overriden from the default build method
  @override
  // this build function returns a Widget
  Widget build(BuildContext context) {
    return Container(color: Color(0xFFFFFFFF));
  }
}

Good news - most IDEs contain snippets to automatically create stateless widgets for you! Just type in stless into your IDE and press TAB or Enter to generate all the code necessary.

If you would like to add parameters for your stateless widget (for example, making a 'message' parameter to pass into a stateless widget that displays that message), we need to use constructors in the same way that classes are constructed. Here's how.

class DisplayMessage extends StatelessWidget {
  // add it to the constructor here after the key, as 'required this.<parameter>'
  DisplayMessage({ Key? key, required this.message }) : super(key: key);

  // initialize it as a 'final' variable (it won't change)
  final String message

  @override
  Widget build(BuildContext context) {
    return Container(
      child: Text(message),
    );
  }
}

This widget would then be instantiated in another parent widget like so:

Scaffold(
  body: Column(
    children: [
      ...
      // instantiating the stateless widget we just created (which is in another file) 
      // with string, the message we want to display
      DisplayMessage(message: 'Hello there!'),
      ...
    ],
  ),
)

Stateful Widgets

Stateful widgets are widgets that can react to certain changes and then be rebuilt. This is useful if we want our app to be interactive. For example, let's say we want to have a counter in our app. Whenever the user presses a '+' button, we want the app to display an increase in a variable we define, 'count'. Here's how.

Note: whenever we want our stateful widget to react to any changes (which requires Flutter to rebuild the page), we use the setState(() {}) method.

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

  @override
  _DisplayCountState createState() => _DisplayCountState();
}

class _DisplayCountState extends State<DisplayCount> {

  // defining a variable, count, inside our widget
  int count = 0;    

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        // display the count as a string
        Text(count.toString()),

        ElevatedButton(
          // the text displayed on the button
          child: Text('Click me to add +'),

          // the code that will execute when the button is pressed
          onPressed: () {
            // setState is called to signal to Flutter to rebuild the widget
            // count is incremented by 1, so the widget will be rebuilt with 
            // a new value displayed in the text widget above
            setState(() {
                count += 1;
            });
          },
        ),
      ],
    );
  }
}

We also have access to IDE snippets for stateful widgets too. Just type in stful.

Constructors in stateful widgets are the same, but they are only declared in the DisplayCount widget and not the _DisplayCountState widget. In the _DisplayCountState widget where you will be putting your code, you can refer to the variable as (widget.[variable]).

class DisplayCount extends StatefulWidget {
  const DisplayCount({Key? key, required this.message}) : super(key: key); 

  final String message;

  @override
  _DisplayCountState createState() => _DisplayCountState();
}

class _DisplayCountState extends State<DisplayCount> {
  ...
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        // refer to the 'message' attribute defined above as widget.message
        Text(widget.message),
        ...
      ],
    );
  }
  ...
}

Stateful widgets are instantiated in the same way as Stateless widgets are.

Stateful widgets are very useful for dealing with anything related to business logic, interactive features, and listening to streams of data on the backend, as we'll see later.

Null Safety

In recent versions of Flutter, null safety was introduced in order to greatly help developers in dealing with notorious null errors.

Essentially, if something like a String is declared and is supposed to be assigned a valid value like 'Hi!', in the case that it somehow is assigned a null value (basically, assigned a value of nothing), then all sorts of problems start to happen - some parts might start missing text, functionalities, etc.

Flutter's null safety helps developers fix these issues by using powerful IDE features that force developers to be more strict with null checking. This means that developers have to account for the situations in which the variables they declare might take on null values.

In null safety, there are 3 important symbols to know about. The '?' symbol, the '!' symbol, and the '??' symbol.

'?'

If we declare a variable that we think might somehow take on a null value, we add the '?' operator to the end of the type declaration to remind us and the IDE to include strict null checking on that variable. Here's an example.

// initializing a string wih a nullable type and assigning it to the 
// return value of this function, fetchSomeDataOrSomething()
String? response = await fetchSomeDataOrSomething(); 
// in the case that the function returned something null and response has a null value,
// it is now safely accounted for with this conditional statement
if (response != null) {
  print(response);
} else {
  print('error');
}

'!'

If we declare a nullable type for a variable but we know for certain that it won't be null, we use the '!' operator at the end of the variable name. Note: try to avoid using this because it bypasses all the null safety checks performed by the IDE.

// fetchSomeData() returns type bool
bool? response = fetchSomeData(); 
// declaring that response will always be a valid value and not null
if (response! == True) { 
  print('function has returned true');
} else {
  print('function has returned false');
}

'??'

When we are assigning a value to a variable, we can check whether it is null or not and assign a value from there. If the value it is assigned is null, we can add the '??' operator and add a default value on the right, in case it is null.

String? response = fetchSomething();
// if response is not null, the 'something' variable will take on the value of response'
// if response is null, the 'something' variable with take on the value on the right side
String something = response ?? 'defaultValue';

Learning Firebase

Since Flutter was developed by Google and Firebase was also developed by Google (which was made specifically for app developers), Flutter and Firebase work very well with each other as frontend and backend tools.

The main backend of most projects will be using a database, which Firebase provides with its Cloud Firestore database. The basic structure of Firestore databases is quite simple, but is very different from conventional, realtime databases like with SQL. Instead, Firestore is a No-SQL database.

This series is extremely good on getting familiar with the structure of Firebase, so please take a look. Episodes 1, 2, and 4 are particularly important.

Get to know Cloud Firestore

Essentially, a Firebase Firestore database is created by making top-level 'collections' which can be things like 'Users', 'Messages', 'Products', etc. These collections can have documents in them.

Documents are specific instances of its parent collection, which can be assigned a number of 'fields' with corresponding values. For example, here's how the Macbook Pro document in the Products collection might look:

left side: collections, middle: documents of the collection, right: fields in this document

Note: I'm accessing a Cloud Firestore database through the Firebase Console on a dummy project I created

The thing about No-SQL databases is that you can create documents in the same collection without the same fields!! For example, the 'Pencil' document might be missing the 'rating' field, but there wouldn't be any errors.

Some other important things to know about Firebase are billing and security rules.

Billing in Firestore is charged not by the size of the database, but by the number of reads and writes to the database. For example, if you create an Electronics product (in the form of a document) and you add it to the database, it counts as one write. If you wanted to update the price of the product, it would also count as 1 write.

If you needed to load all the 'Food' collection's products, Firebase would charge you 1 read per every document in the collection.

However, Firebase is quite generous with its limits. But, if you would like to take your app to production (put it into the real world), it's best to be wary of how billing works in order to optimize your database calls.

Check the Firebase pricing page to learn about the limits for the free tier.

More really good resources:

Connecting Firebase with Flutter

Now that we know about the most important part of Firebase (the Firestore database), how do we get access to that data in Flutter?

StreamBuilder

We can use a StreamBuilder for this purpose. A 'Stream' is essentially just a stream of data that we are constantly watching for changes in. One end of the stream is the Firestore database. The other end of the stream is our app.

Thus, when something changes in the Firestore database (say, a new product is added), that change goes down the data stream and gets noticed by our Flutter app. Once that change is noticed, the StreamBuilder widget rebuilds itself in order to incorporate that change (the new product now appears on our app).

Here's the syntax:

StreamBuilder(
  // gets an instance of a Firestore database and retrieves 'snapshots' of the Macbook Pro document
  stream: FirebaseFirestore.instance.collection('Products').doc('Macbook Pro').snapshots(),
  // builder defines what will be built on the app using this 'snapshot' data (the stream data)
  // Firestore collections are of type QuerySnapshot
  // Firestore documents are of type DocumentSnapshot
  // Both are referred to as AsyncSnapshots because they are asynchronous snapshots
  builder: (BuildContext context, AsyncSnapshot<DocumentSnapshot> snapshot) {
    // check that there is data in the stream and that it has finished loading
    if (snapshot.hasData) {
      return Container(
        // snapshot.data gives you access to all the fields in the document
        // get the values of the fields by using square brackets and the 
        // name of the field, like so
        child: Text(snapshot.data['name'])
      ),
    }, else {
      // if there's no data yet, show a loading sign
      return CircularProgressIndicator();
    }
  },
)

It might look complicated, but it really isn't. On one side you are accessing a stream of data, whether it be a collection or document, and you are building a widget that has access to that data through the 'snapshot' variable. If there are any changes that the StreamBuilder detects on Firestore's end, the widget will be rebuilt.

FutureBuilder

StreamBuilders are great, but what about if you didn't need to listen to changes on Firestore's end? What if you just wanted to retrieve some information, say the price of the Macbook, and be done with it (you might know that the values won't change)?

We can do that with a FutureBuilder.

FutureBuilders take in an asynchronous function as a parameter, and a builder to build something once that function has finished executing (similar to the StreamBuilder). In our example, our asynchronous function or 'future' (as the FutureBuilder calls it) would be retrieving the price of the Macbook, and our builder would be the widgets displaying that price.

// defining an async function that returns an int
Future<int> retrieveMacbookPrice() async {
  // PS here's how to retrieve a single document from Firestore - 
  // in our case, the Macbook document
  var document = await FirebaseFirestore.instance.collection('Products').doc('Macbook Pro').get();
  // The data you get back (the document and its fields) will be a dictionary that maps 
  // keys (type String) to values (type dynamic)
  Map<String, dynamic> macbookData = document.data();

  int macbookPrice = macbookData['price'];
}

FutureBuilder(
  // builder will only build after this 'future' function is done executing
  future: retrieveMacbookPrice(),
  // the 'snapshot' here refers to what is returned from the future!
  builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
    if (snapshot.hasData) {
      // data from the snapshot is accessed like so
      int price = snaphot.data['price']
      return Container(
        // convert int to string
        child: Text(price.toString()),
      );
    } else {
      // if there's no data yet, show a loading sign
      return CircularProgressIndicator();
    }
  }
)

Whew, That was a lot! But guess what... Now that you know about how Firebase, FutureBuilders, and StreamBuilders work, you are very far in your journey to creating robust apps in Flutter.

State management

State management is a very important concept in Flutter that goes like this:

Say you want to make an app that keeps track of your user's profile and information. After they log in with their username and password, you want to display their username in every page of the app as a greeting (for example, to say 'Hello, [name]!'). How would you do this? How do you pass around the user's values of 'username' and 'password' throughout the whole app?

You could pass the username as a parameter for all the stateless and stateful widgets that make up the different pages. But in reality, you want something that holds that username value that you can access across all screens / pages.

This can be done using a 'Provider' widget, a built in state management solution.

A Provider is called a 'Provider' because it is a parent widget that 'provides' a value to pass down to the child widget, so that the child widget has access to everything from the value / entity. In our example, if we had a 'Cart' class that we wanted to access in child widgets, it would look something like this:

Provider(
  create: (context) => CartModel(),
  child: MyApp(),
)

Thus, in the MyApp child widget, we would have access to the CartModel and all of its methods and such. You can instantiate the CartModel class to access the data in two ways:

// 1st way
Provider.of<CartModel>(context).removeAllItems();

// 2nd way
// context.watch listens for changes in CartModel - if data changes, the parent will rebuild
// whatever is necessary
context.watch<CartModel>().removeAllItems();
// context.read returns CartModel / the model of interest without listening to changes in 
// the data
context.read<CartModel>().removeAllItems();

This calls Provider to look at the model that is of type CartModel, and calls the method removeAllItems(). In the 2nd way, the object of type CartModel (whatever is between < >) is instantianted by parentheses -> context.read< >().

What if we wanted to access another piece of data that requires state management - say, the user's preferences for their color theme in the app? We could create a class called 'UserPreferences', but how would we have access to it on top of the CartModel class?

One way to do it would be to nest Providers.

Provider(
  create: (context) => CartModel(),
  child: Provider(
    create: (context) => UserPreferences(),
    child: MyApp(),
  ),
)

So we would have access to both the UserPreferences model and CartModel in MyApp. But you can probably tell that this gets unwieldly, fast, right? That's where MultiProvider comes into play.

The 'MultiProvider' widget allows us to define multiple 'providers' at the very top of the app (main.dart), where ALL the child widgets have access to each of the providers.

MultiProvider(
  providers: [
    Provider<CartModel>(create: (_) => CartModel()),
    Provider<UserPreferences>(create: (_) => UserPreferences()),
  ],
  child: MyApp(),
)

What a natural progression!

That's the basics of state management. Take a look at the extra resources listed below to get more familiar with these concepts and syntax.

Best Practices

Best Practices are always important to keep in mind when developing any large projects in Flutter.

Folder Structure

To maintain a large project, make sure your folder structure is correctly organized.

Here's how folders are usually structured:

As we saw before, lib is where you will put all your flutter code. Flutter then converts its code into android and ios code to make native apps, which can be found in the android and ios folders. Any images, svgs, or pictures you use should be placed in an 'assets' folder, which you have to create.

In the lib folder, you should split code up into screens, models, services, widgets, and constants. Main.dart will be your wrapper file.

Constants is used for placing constants.dart, which usually defines ThemeData and color schemes for your app, to make it easier for your app to conform to a certain style. For example, I usually define kPrimaryColor and kSecondaryColor in the constants.dart file. You can also use a theme.dart file to create ThemeData objects.

Models are the classes you want to create to make it easier to work with data in Flutter. For example, you might want to create a User class that has properties of 'username', 'nickname', 'age', etc. In the models folder, create and name your file based on what you would like your class to be called. If I wanted to make...

class User {
  String usernamename;
  String nickname;
  int age;
}

Then I would name the file user.dart (if it is two words, simply use an underscore instead of a space -> food_item.dart).

Screens is the folder where you will place most of your code - the UI code for all of your screens. To create a new screen, create a folder and name it the screen, and place your code in that subfolder. This way, all your screens will be different folders in the 'screens' folder. In your specific screen folder, name the main file (name_of_screen).dart.

If your screen has many components to it, create a components folder in the screen's directory.

Services is used for putting all the classes that contain any business logic. These follow the same folder conventions as the models folder.

Widgets is used for putting all widgets you custom created that you use for multiple screens. For example, if you created your own Button widget that you want to use on both the login and sign_in screens, just put that Button file into the widgets folder.

Separate Business Logic from Frontend

Business logic is essentially any code that doesn't directly have to do with the layout of the app. For example, if you had a login screen, the UI would be the Column, TextField, and ElevatedButton widgets. The business logic would be how the user would sign in to the backend server you are using (for example, Firebase).

It is generally a good idea to keep these separated so you don't mix backend / dealing with data and frontend, which can lead to messy and confusing code. If I wanted to look into the code for the product_details screen, why would I want to see how the product is dealt with on the backend? It's cleaner to separate the two paradigms.

What this means for us is that we should place as much of the business logic / backend code in the 'services' folder as we can, and not in the 'screens' folder. I usually do this by defining an 'APIServices' class that has a number of methods that deals with the business logic.

Abstract as much as possible (make more widgets)

In Flutter, it is in your best interest to extract as much code as you can. What this means is that whenever you have a part of the widget tree that is dedicated to a single use case, extract it into its own widget and put it somewhere else. Here's an example.

// products_screen.dart

Scaffold(
  // Column widget to lay out everything on the page vertically
  body: Column(
    children: [
      // nested column widget dedicated to displaying electronics
      Column(
        children: [
          Container(child: Text('Electronics')),
          Text('Macbook pro'),
          Text('iPhone'),
          Text('Galaxy Buds'),
        ],
      ),
      // nested column widget dedicated to displaying food
      Column(
        children: [
          Container(child: Text('Food items')),
          Text('Jelly beans'),
          Text('Peanut Butter'),
          Text('Apples'),
        ],
      ),
    ],
  ),
)

This would be putting the 'Food items' section and 'Electronics' section into a single widget tree, which gets messy and confusing if the project grows bigger. As best practices, here's what it would look like instead.

// screens/products/products_screen.dart

Scaffold(
  body: Column(
    children: [
      // Extracted widgets (put the widgets into their own file in the 'components' directory of this screen's directory)
      ElectronicsSection(),
      FoodItemsSection(),
    ],
  ),
)

// screens/products/components/electronics_section.dart

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

  // same widgets, just put into the build function as a returned value
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Container(child: Text('Electronics')),
        Text('Macbook pro'),
        Text('iPhone'),
        Text('Galaxy Buds'),
      ],
    );
  }
}


// screens/products/components/food_items_section.dart

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

  // same widgets, just put into the build function as a returned value
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Container(child: Text('Food items')),
        Text('Jelly beans'),
        Text('Peanut Butter'),
        Text('Apples'),
      ],
    );
  }
}


The result is much cleaner and much easier to debug and code with.

Testing

Creating unit tests for your Flutter app are a very convenient way to make sure adding new features doesn't break your code.

Tests are written to automate the process of checking certain functionalities in your app. For example, you can write a unit test to make sure the login screen and business logic is working correctly, which you might run everytime you make changes to other parts of your app.

Take a look at these resources to learn about testing in flutter.

Helpful Resources

Now that you know the basic syntax of Flutter and how it works, you need to put this knowledge into practice! I recommend following along with Flutter App Build tutorials to see just how advanced coders develop apps, and then try to code an app with only UI components by yourself (you can look on dribbble for ui inspiration). Good luck!

These are all the compiled resources, listed from more beginner-friendly to more advanced.

Dart

Flutter

Flutter App Tutorials

Firebase

State management

If this feels a little bit overwhelming, don't feel discouraged. Many times I was stuck, but once I understood a new concept I quickly made tons of progress. It's never too late. Be glad that you are here in the first place!

I hope you enjoyed this comprehensive guide!

Download Details:

Author: antz22
Official Website: https://github.com/antz22/ultimate-guide-to-flutter

#flutter #dart 

What is GEEK

Buddha Community

The Ultimate Guide to App Development with Flutter
Fredy  Larson

Fredy Larson

1595059664

How long does it take to develop/build an app?

With more of us using smartphones, the popularity of mobile applications has exploded. In the digital era, the number of people looking for products and services online is growing rapidly. Smartphone owners look for mobile applications that give them quick access to companies’ products and services. As a result, mobile apps provide customers with a lot of benefits in just one device.

Likewise, companies use mobile apps to increase customer loyalty and improve their services. Mobile Developers are in high demand as companies use apps not only to create brand awareness but also to gather information. For that reason, mobile apps are used as tools to collect valuable data from customers to help companies improve their offer.

There are many types of mobile applications, each with its own advantages. For example, native apps perform better, while web apps don’t need to be customized for the platform or operating system (OS). Likewise, hybrid apps provide users with comfortable user experience. However, you may be wondering how long it takes to develop an app.

To give you an idea of how long the app development process takes, here’s a short guide.

App Idea & Research

app-idea-research

_Average time spent: two to five weeks _

This is the initial stage and a crucial step in setting the project in the right direction. In this stage, you brainstorm ideas and select the best one. Apart from that, you’ll need to do some research to see if your idea is viable. Remember that coming up with an idea is easy; the hard part is to make it a reality.

All your ideas may seem viable, but you still have to run some tests to keep it as real as possible. For that reason, when Web Developers are building a web app, they analyze the available ideas to see which one is the best match for the targeted audience.

Targeting the right audience is crucial when you are developing an app. It saves time when shaping the app in the right direction as you have a clear set of objectives. Likewise, analyzing how the app affects the market is essential. During the research process, App Developers must gather information about potential competitors and threats. This helps the app owners develop strategies to tackle difficulties that come up after the launch.

The research process can take several weeks, but it determines how successful your app can be. For that reason, you must take your time to know all the weaknesses and strengths of the competitors, possible app strategies, and targeted audience.

The outcomes of this stage are app prototypes and the minimum feasible product.

#android app #frontend #ios app #minimum viable product (mvp) #mobile app development #web development #android app development #app development #app development for ios and android #app development process #ios and android app development #ios app development #stages in app development

Hire Dedicated Flutter App Developer USA| Flutter App Developers

Hire Flutter App Developers: WebClues Infotech is a Flutter App Development company. Our Flutter mobile app development team can create cross-platform apps for different industry verticals. Our Flutter developers will help you extend your business’s scope by developing enhanced functionality and a feature-rich app. To provide a rich user experience to your users, hire dedicated Flutter app developers from WebClues Infotech today!

#hire flutter app developers #hire dedicated flutter app developer usa #hire flutter app developer usa #hire dedicated flutter app developer #hire flutter developer #flutter app development company

Google's Flutter 1.20 stable announced with new features - Navoki

Flutter Google cross-platform UI framework has released a new version 1.20 stable.

Flutter is Google’s UI framework to make apps for Android, iOS, Web, Windows, Mac, Linux, and Fuchsia OS. Since the last 2 years, the flutter Framework has already achieved popularity among mobile developers to develop Android and iOS apps. In the last few releases, Flutter also added the support of making web applications and desktop applications.

Last month they introduced the support of the Linux desktop app that can be distributed through Canonical Snap Store(Snapcraft), this enables the developers to publish there Linux desktop app for their users and publish on Snap Store.  If you want to learn how to Publish Flutter Desktop app in Snap Store that here is the tutorial.

Flutter 1.20 Framework is built on Google’s made Dart programming language that is a cross-platform language providing native performance, new UI widgets, and other more features for the developer usage.

Here are the few key points of this release:

Performance improvements for Flutter and Dart

In this release, they have got multiple performance improvements in the Dart language itself. A new improvement is to reduce the app size in the release versions of the app. Another performance improvement is to reduce junk in the display of app animation by using the warm-up phase.

sksl_warm-up

If your app is junk information during the first run then the Skia Shading Language shader provides for pre-compilation as part of your app’s build. This can speed it up by more than 2x.

Added a better support of mouse cursors for web and desktop flutter app,. Now many widgets will show cursor on top of them or you can specify the type of supported cursor you want.

Autofill for mobile text fields

Autofill was already supported in native applications now its been added to the Flutter SDK. Now prefilled information stored by your OS can be used for autofill in the application. This feature will be available soon on the flutter web.

flutter_autofill

A new widget for interaction

InteractiveViewer is a new widget design for common interactions in your app like pan, zoom drag and drop for resizing the widget. Informations on this you can check more on this API documentation where you can try this widget on the DartPad. In this release, drag-drop has more features added like you can know precisely where the drop happened and get the position.

Updated Material Slider, RangeSlider, TimePicker, and DatePicker

In this new release, there are many pre-existing widgets that were updated to match the latest material guidelines, these updates include better interaction with Slider and RangeSliderDatePicker with support for date range and time picker with the new style.

flutter_DatePicker

New pubspec.yaml format

Other than these widget updates there is some update within the project also like in pubspec.yaml file format. If you are a flutter plugin publisher then your old pubspec.yaml  is no longer supported to publish a plugin as the older format does not specify for which platform plugin you are making. All existing plugin will continue to work with flutter apps but you should make a plugin update as soon as possible.

Preview of embedded Dart DevTools in Visual Studio Code

Visual Studio code flutter extension got an update in this release. You get a preview of new features where you can analyze that Dev tools in your coding workspace. Enable this feature in your vs code by _dart.previewEmbeddedDevTools_setting. Dart DevTools menu you can choose your favorite page embed on your code workspace.

Network tracking

The updated the Dev tools comes with the network page that enables network profiling. You can track the timings and other information like status and content type of your** network calls** within your app. You can also monitor gRPC traffic.

Generate type-safe platform channels for platform interop

Pigeon is a command-line tool that will generate types of safe platform channels without adding additional dependencies. With this instead of manually matching method strings on platform channel and serializing arguments, you can invoke native class and pass nonprimitive data objects by directly calling the Dartmethod.

There is still a long list of updates in the new version of Flutter 1.2 that we cannot cover in this blog. You can get more details you can visit the official site to know more. Also, you can subscribe to the Navoki newsletter to get updates on these features and upcoming new updates and lessons. In upcoming new versions, we might see more new features and improvements.

You can get more free Flutter tutorials you can follow these courses:

#dart #developers #flutter #app developed #dart devtools in visual studio code #firebase local emulator suite in flutter #flutter autofill #flutter date picker #flutter desktop linux app build and publish on snapcraft store #flutter pigeon #flutter range slider #flutter slider #flutter time picker #flutter tutorial #flutter widget #google flutter #linux #navoki #pubspec format #setup flutter desktop on windows

Juned Ghanchi

1621572917

Flutter App Development Company India, Flutter App Developers for Hire

We build the best-in-class flutter apps that let your business work with great capacity and with a lower operating cost. And businesses can perform well and achieve higher productivity with minimal efforts.

Using app built by our developers eliminates the need to hire separate programmers for native platforms. The apps developed with the Flutter SDK package are heavily optimized for native style platforms and API integrations.

Hire the flutter app developers in India and get natively compiled top-notch app for mobile, web, and desktop.

#flutter app development company india #flutter app developers india #hire flutter developers india #flutter app development company #flutter app developers #hire flutter developers

Best Flutter App Development Company

Are you looking for the best flutter app development company? Then AppClues Infotech is the leading flutter app development company in USA offering the best service worldwide. We focused on developing hybrid mobile apps on Android & iOS and assures our end-user about exceptional and functionally-rich mobile apps.

For more info:
Website: https://www.appcluesinfotech.com/
Email: info@appcluesinfotech.com
Call: +1-978-309-9910

#top flutter app development company in usa #best flutter app development service #best flutter app development company #hire flutter app developers #flutter app development company #expert flutter app development company