This is the continuation of "how I built the Friends Roster app" saga. If you missed the previous parts in this series, do check them out — part 1part 2part 3 and part 4. In this post I will go into a little more details on how I built the screens with the help of Flutter plugins.

One of the best features of Flutter is plugins. There are a number of plugins available that do all the heavy lifting for you. For example there are plugins to login to Google account, read contacts from phone, access call logs etc. And using them is quite easy too. All you have to do is add the plugin in the pubspec.yaml file of your project and run with it! Here are the plugins I used in my project.

firebase_core: ^0.5.3
firebase_auth: ^0.18.4
google_sign_in: ^4.5.6
url_launcher: ^5.7.10
cloud_firestore: ^0.14.4
timeago: ^2.0.29
clock: ^1.0.1
contacts_service: ^0.4.6
permission_handler: ^5.0.1
call_log: ^2.1.0

No wonder I did not have to write a lot of code!

Login screen

Like I mentioned in my previous post, I have added a login page so I can have different users using the app. On top of that I wanted to store my friends data on the cloud so that I can manage them on multiple devices (laptop and phone). What better way to do all that but to use Firebase Cloud Store. It gives a convenient way to synchronize data across all devices. If I am going to use Firebase, might as well use Firebase Authentication to login the user as well. I decided to use Google for authentication. Thankfully flutter already has plugins for Google authenticated login.

firebase_core: ^0.5.3
firebase_auth: ^0.18.4
google_sign_in: ^4.5.6

If you remember, the reason I used Flutter is because I can run the app both on web and on android using the same code base. So the login code is slightly different for both platforms as you will see marked in bold below.

class LoginScreen extends StatelessWidget {
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: () => _signIn(context),
      child: Text("LOGIN"),

  void _signIn(BuildContext context) async {
    if (Dependency.isWeb) {
      await Dependency.auth.signInWithRedirect(GoogleAuthProvider());
    } else {
      GoogleSignInAccount googleUser = await Dependency.googleSignIn.signIn();
      GoogleSignInAuthentication googleAuth = await googleUser.authentication;
      GoogleAuthCredential credential = GoogleAuthProvider.credential(
        accessToken: googleAuth.accessToken,
        idToken: googleAuth.idToken,
      await Dependency.auth.signInWithCredential(credential);

Bulk Actions Screen

For bulk actions on android, the app uses the following plugins to

  1. Request permissions to read contacts data
  2. Read contacts
  3. Store the contacts in Firebase Cloud Store

permission_handler: ^5.0.1
contacts_service: ^0.4.6
cloud_firestore: ^0.14.4

The best part is that the plugins really simplify the process of asking permissions to access contacts and fetching data from the contacts DB.

Future<List<Friend>> getFriends() async {
  var obtainedStatus = await Permission.contacts.request();
  setState(() => status = obtainedStatus);
  if (status.isGranted) {
    contacts = await ContactsService.getContacts();
    return contacts
        .where((contact) => Friend.isValidContact(contact))
        .map((contact) => Friend.fromContact(contact))
  } else {
    return [];

Finally the friends are stored in Firebase Cloud Store.

// Some code removed for brevity
addOrUpdateFireStoreFriends(List<Friend> friends) async {
  final DocumentReference userDocRef =
  DocumentSnapshot userSnapshot = await userDocRef.get();
  if (!userSnapshot.exists) {
    print('Adding new user to store');
    await userDocRef.set({});
    userSnapshot = await userDocRef.get();

  CollectionReference friendsCollection = userDocRef
  friends.forEach((friend) {
    batch.set(friendsCollection.doc(, friend.toJson());
  await batch.commit();

Since this post is already pretty long, I will continue where I left off in my next one.