Flutter By Example Part 1

In previous blogs we saw very basic things with flutter, now going forward i would like to take this forward by developing a real app for my personal project and use flutter for it.

Drawer

Let’s see how to implement AppDrawer as it is something which i need in the app.

It turns out its quite easy just follow this guide https://flutter.dev/docs/cookbook/design/drawer

Code Splitting

Next i want to show some content on the app similar to this

For this i need to make a separate widget. At this stage its better to split code in multiple files. Since the app will be quite big we cannot just keep all in one single main.dart file.

The way i will split it is

lib/screens == this will have all the main screens or pages

lib/widgets == this will have the different widgets used in the pages

lib/main.dart

This is how the code looks now

and our home.dart looks like this

import 'package:flutter/material.dart';
import "package:myapp/widget/home/amc.dart";
import 'package:myapp/widget/drawer.dart';

class HomeScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Home'),
      ),
      body: Center(child: AMCSummary()),
      drawer: AppDrawer(),
    );
  }
}

This way we are able to split the entire code base in more readable files

Data (JSON)

Most of the mobile app’s requires interaction with rest api and json data.

Even in the above widget we made, we need data from rest api. Let’s see by example how to parse JSON data first.

You can also read about it here https://flutter.dev/docs/development/data-and-backend

So to start let’s start with basic json parsing.

FlatButton(
                  child: Text("Test JSON"),
                  onPressed: () {
                    String jsonString =
                        "{\"name\": \"John Smith\",\"email\": \"[email protected]\"}";

                    Map<String, dynamic> user = jsonDecode(jsonString);

                    print('Howdy, ${user['name']}!');
                    print('We sent the verification link to ${user['email']}.');
                    
                  },
                )

We added a “FlatButton” to our app and onPress did a print.

As dart is a strongly typed language this is not a very effective way of doing this. We should define a model class and map the json to a model class object.

First we define a User object like this

//model/user.dart

class User {
  final String name;
  final String email;

  User(this.name, this.email); //constructor

  User.fromJson(Map<String, dynamic> json)
      : name = json["name"],
        email = json["email"] {
    print("fromJSON Called"); //just for debugging
  }
  Map<String, dynamic> toJson() => {"name": name, "email": email};
}

Since we are new to dart language, let’s see what this means exactly

Both User() and User.fromJson are constructors in dart read here https://dart.dev/guides/language/language-tour#constructors

https://dart.dev/guides/language/language-tour#final-and-const

In our code we include the file model/user.dart

FlatButton(
                  child: Text("Test JSON"),
                  onPressed: () {
                    String jsonString =
                        "{\"name\": \"John Smith\",\"email\": \"[email protected]\"}";

                    User user = User.fromJson(jsonDecode(jsonString));

                    print('Howdy, ${user.name}!');
                    print('We sent the verification link to ${user.email}.');
                  },

See full code here https://github.com/excellencetechnologies/flutter_start/commit/544b474354a4de829e01ad5a7978bbcb1bf71826

API Call

https://flutter.dev/docs/cookbook/networking/fetch-data

Read above how to do it and let’s see it in practice in our app.

First before understanding api calls, we need to understand the “Future” object in dart. Future object is used for doing any async operation and is managed via keywords “async”, “await” https://dart.dev/codelabs/async-await read about it in detail here.

Let’s use it in our code

                    Future<Post> fetchPost() async{
                      final response = await http
                          .get('https://jsonplaceholder.typicode.com/posts/1');

                      if (response.statusCode == 200) {
                        // If server returns an OK response, parse the JSON.
                        return Post.fromJson(json.decode(response.body));
                      } else {
                        // If that response was not OK, throw an error.
                        throw Exception('Failed to load post');
                      }
                    }

                    Post post = await fetchPost();
                    print(post.id);

For api’s we are using ” https://jsonplaceholder.typicode.com/ ” this is a popular website to try out different api’s

So basically we fire the api check if response is 200 i.e success. If yes, return the Post object, else we throw Exception.

Next, we call the function. Do note we are using “await” keyword when calling the function. Because we are calling the “await” function we are able to get Post object and not the Future object.

Also its best practice to move all api’s called to “services” folder in a separate file.

We define a service/post.dart

//services/post.dart

import 'package:myapp/model/post.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';

class PostService {
  static Future<Post> fetchPost() async {
    final response =
        await http.get('https://jsonplaceholder.typicode.com/posts/1');

    if (response.statusCode == 200) {
      // If server returns an OK response, parse the JSON.
      return Post.fromJson(json.decode(response.body));
    } else {
      // If that response was not OK, throw an error.
      throw Exception('Failed to load post');
    }
  }
}
// our component

FlatButton(
                  child: Text("Test JSON"),
                  onPressed: () async {
                    Post post = await PostService.fetchPost();
                    print(post.id);
                  },
                )

Cool! Next, lets see how to display this data in our Widget.

To show this data in our widget, we need a “statefull” widget

This is a sample widget for this

class MyDynamicData extends StatefulWidget {
  @override
  State<MyDynamicData> createState() {
    return MyDynamicDataState();
  }
}

class MyDynamicDataState extends State<MyDynamicData> {
  String textToDisplay = "";
  bool showLoader = false;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        showLoader ? CircularProgressIndicator() : Container(),
        Text("$textToDisplay "),
        FlatButton(
          child: Text("Test JSON"),
          onPressed: () async {
            setState(() {
              showLoader = true;
              textToDisplay = "";
            });
            Post post = await PostService.fetchPost();
            print(post.id);
            setState(() {
              showLoader = false;
              textToDisplay = post.title;
            });
          },
        )
      ],
    );
  }
}

Flutter comes with an out-of-box widget called FutureBuilder, https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html this is useful to make simple components with async features. This is mainly for very simple purposes

Also read this once https://flutter.dev/docs/cookbook/networking/fetch-data#5-moving-the-fetch-call-out-of-the-build-method this is relevant in cases when we want data to load automatically without button tap etc.

List View

Another important thing we use in app’s a lot is List (scrollable list).

In my app i need to show a List (data) after fetching the data from api. Let’ see how i have implemented it. But before doing see make sure to go through this https://flutter.dev/docs/cookbook#lists

This has examples of horizontal lists, grids, and other common uses for list view.

class AMCList extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new FutureBuilder<List<AMC>>(
      future: AMCService.getAMCList(),
      builder: (context, snapshot) {
        if (!snapshot.hasData) return CircularProgressIndicator();
        List<AMC> amcs = snapshot.data;
        return ListView.builder(
            itemCount: amcs.length,
            itemBuilder: (context, index) {
              return ListTile(
                title: Text('${amcs[index].name}'),
              );
            });
      },
    );
  }
}

One thing is very critical, listView needs a parent element which provides it a size. So either use it in body directly else if you are using it row, column make sure to use expanded or flexible etc and provide a height else you will get an error like this https://stackoverflow.com/questions/52801201/flutter-renderbox-was-not-laid-out

https://github.com/excellencetechnologies/flutter_start/commit/990e9cb2100b69a1daabada5224d0ebf593a8910

Routing

There is a very awesome guide by flutter on routing https://flutter.dev/docs/development/ui/navigation there is really nothing for me to explain on this. Just read the guide.

I will try to implement routing in the current app we have. I want to implement a feature in which when i click on an element in my ListView, i am able to see details about it.

First i will setup a new blank screen called “AMCScreen”

import 'package:flutter/material.dart';
import 'package:myapp/widget/drawer.dart';

class AMCScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('AMC'),
      ),
      body: Center(
        child: Text("AMC Page"),
      )  ,
      drawer: AppDrawer(),
    );
  }
}

Next i add this screen to my route

import 'package:flutter/material.dart';
import 'package:myapp/screens/home.dart';
import 'package:myapp/screens/amc.dart';

void main() => runApp(MaterialApp(
      title: 'My First Flutter App',
      theme: ThemeData.light(),
      initialRoute: "/",
      routes: {
        '/': (context) => HomeScreen(),
        '/amc': (context) => AMCScreen()
      }
    ));

Then in my listView i do this

return ListTile(
                onTap: () {
                  print("tapped");
                  Navigator.pushNamed(context, '/amc');
                },
                title: Text('${amcs[index].name}'),
              )

So when i click on ListView it takes me a new page and works!

See code here https://github.com/excellencetechnologies/flutter_start/commit/a63a9f7e6cd4f0062de47a4276ad1cac9357d381

Next, we need to pass AMC details to new screen to fetch data about the AMC.

The screen AMC requires AMC object to work. Without that it cannot work at all, so to make this happen we create AMCScreen object like this

import 'package:flutter/material.dart';
import 'package:myapp/widget/drawer.dart';
import 'package:myapp/model/amc.dart';

class AMCScreen extends StatelessWidget {
  final String routeName = "/amcScreen";
  final AMC amc;
  AMCScreen({Key key, @required this.amc}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(amc.name),
      ),
      body: Center(
        child: Text(amc.name),
      )  ,
      drawer: AppDrawer(),
    );
  }
}

Note how we have “amc” variable as required in the constructor. Also inline with this we need to remove AMCScreen() from the route object because we don’t have an amc object in routes.

Next in our ListView we make this change

return ListTile(
                onTap: () {
                  print("tapped");
                  Navigator.push(context, MaterialPageRoute(
                    builder: (context) => AMCScreen(amc: amcs[index],)
                  ));
                },
                title: Text('${amcs[index].name}'),
              );

This means we are directly push the new screen to navigator.

Also on the AMCScreen we need a back button so we add it like this

appBar: AppBar(
        automaticallyImplyLeading: true,
        leading: IconButton(icon:Icon(Icons.arrow_back),
          onPressed:() => Navigator.pop(context, false),
        ),
        title: Text(amc.name),
      )

current state of code looks like this https://github.com/excellencetechnologies/flutter_start/commit/f97b070bb2cbd271bc2ba4bcbcd4191db846662a

Next on the new screen, we need to fire api based on amc_id and get further detail to display on the screen.

To start with lets create a Statefull widget

class AMCFetchFund extends StatefulWidget {
  final AMC amc;
  AMCFetchFund({Key key, @required this.amc}) : super(key: key);

  @override
  _AMCFetchFundState createState() => new _AMCFetchFundState();
}

First notice how i have made this.amc @required and passing amc object to it. But then also notice, the State (AMCFetchFundState) i am not passing amc object??

class _AMCFetchFundState extends State<AMCFetchFund> {
  List<Fund> funds;
  @override
  void initState() async {
    super.initState();
    List<Fund> newfunds = await AMCService.getFunds(widget.amc);
    //.....
    setState(() {
      funds = newfunds;
    });
  }
.....//

Ok, lets pause here. First notice “widgets.amc” is used access the amc object! Second the above code give an error. It turn’s out we cannot use async function inside initState() or setState() at all!

https://stackoverflow.com/questions/49135632/flutter-best-practices-of-calling-async-codes-from-ui

So correct way to do this is

class _AMCFetchFundState extends State<AMCFetchFund> {
  @override
  Widget build(BuildContext context) {
    return FutureBuilder(
      future: AMCService.getFunds(widget.amc),
      builder: (BuildContext context, AsyncSnapshot<List<Fund>> snapshot) {
        if (snapshot.hasData) {
          return ListView.builder(
              itemCount: snapshot.data.length,
              itemBuilder: (context, index) {
                return ListTile(
                  title: Text('${snapshot.data[index].name}'),
                );
              });
        } else {
          return CircularProgressIndicator();
        }
      },
    );
  }
}

after doing this, we are able to see all data of the amc.

https://github.com/excellencetechnologies/flutter_start/commit/20fc57ac0f67d6569d17dc8331164093ecd175c6

Let’s continue further in next blog post

excellence-social-linkdin
excellence-social-facebook
excellence-social-instagram
excellence-social-skype