Contact List Example

→ Display a list of contacts with images (Xamarin resources)

→ Flutter Bloc Pattern

 

The latest version of all the official Flutnet samples can be found in the official GitHub repository: https://github.com/flutnet/samples/

 

Overview

 

This example will not show new features about Flutnet, but is intended to show a real use app case, more complex that our previous examples. In particural this app will show a list of contacts that will be loaded dinamically requesting a chunk of data to Xamarin using a specific service (like a remote data provider).

The flutter project will use one of the most popular flutter package library, called flutter_bloc. This exemple will referer to the version ^v3.2.0, so this code could not be compatible with the latest stable version at the time you read this guide. If you want to go deeper read the official documentation about the Dart bloc library.

 

In Xamarin we'll define a PlatformService called ContactService that will provide one PlatformOperation:

  1. public ContactQueryResult QueryContacts (string name, string lastname, int startIndex = 0, int limit = 1000)

with two PlatformData class:

  1. Contact, with the contact information (like Name, Lastname, PhoneNumber, Image, etc...)
  2. ContactQueryResult, with the chunk of contacts requested and the total count.

 

In Flutter we'll define a ContactsPage that will show the list of contacts. Associated with this page the ContactsPageBloc willl handle the business logic of the page defining:

  • the states of the page:
    1. ​​UnContactsPageState (uninitialized page)
    2. ​​InContactsPageState (initialized page) → with the list of contact to see
    3. ErrorContactsPageState (some error on page)
       
  • the events of the page:
    1. ​​LoadContacts → event that will load the page at startup
    2. ​​FetchContacts → event that will a chunk of contacts when the user scroll the list

 

 


Create the project

Create a project named "FlutnetContactList" using the Flutnet Console.

The generated project structure will contains the "FlutnetContactList" solution and all his related projects:

  • Xamarin
    • FlutnetContactList.Android
    • FlutnetContactList.iOS
    • FlutnetContactList.ServiceLibrary (net standard)
  • Flutter
    • flutnet_contanct_list (flutter module, where you develop the UI)
    • flutnet_contanct_list_bridge (bridge package, genetared by the $ flutnet pack command)

 

 


Define the ServiceLibrary (PlatformService, Operations, etc...)

 

In the FlutnetContacList.ServiceLibrary project we add several images as embedded resources that will be used to define the fake contacts database.

Here all the project classes.

Contact.cs

using Flutnet.ServiceModel;

namespace FlutnetContacList.ServiceLibrary
{
    [PlatformData]
    public class Contact
    {
        public string Name { get; set; }

        public string Lastname { get; set; }

        public string  PhoneNumber { get; set; }

        public int NationId { get; set; }

        public byte[ ] Image { get; set; }
    }
}

 

ContactQueryResult.cs

using Flutnet.ServiceModel;

namespace FlutnetContacList.ServiceLibrary
{
    [PlatformData]
    public class ContactQueryResult
    {
        public int TotalCount { get; set; }

        public IEnumerable<Contact> Contacts { get; set; }

    }
}

 

ContactService.cs

using Flutnet.ServiceModel;

namespace FlutnetContacList.ServiceLibrary
{
    [PlatformService]
    public class ContactService
    {
        // Fake database resources
        private readonly List<Contact> _contactsTemplate = new List<Contact>()
        {
            new Contact()
            {
                Name = "Adams",
                Lastname = "Wilson",
                Image = ResourceReader.ReadByteResources(typeof(ContactService),"Profile1.PNG"),
                PhoneNumber = "+1-202-555-0177",
                NationId = 0,
            },
            new Contact()
            {
               .....
            },
            ...
        };

        // Support variables
        private readonly Random _random = new Random();
        private readonly List<Contact> _contacts = new List<Contact>();

        // Load a fake contact list
        void _loadData()
        {
            for (int i = 0; i < 100; i++)
            {
                int randomIndex = _random.Next(0, _contactsTemplate.Count - 1);
                Contact randomContact = _contactsTemplate[randomIndex];

                _contacts.Add(randomContact);
            }
        }

        // Service of contancts contructor
        public ContactService()
        {
            _loadData();
        }

        [PlatformOperation]
        public ContactQueryResult QueryContacts(string  name, string  lastname, int startIndex = 0, int limit = 1000)
        {
            // Filter the contacts by name and lastname, with limit count
            List<Contact> contacts = _contacts.Where(c =>
                    (string.IsNullOrEmpty(name) || c.Name == name) &&
                    (string.IsNullOrEmpty(lastname) || c.Lastname == lastname)
                )
                .Skip(startIndex < 0 ? 0 : startIndex)
                .Take(limit < 0 ? 0 : limit).ToList();
            
            // Get the total query count
            int count = _contacts.Count(c => (string.IsNullOrEmpty(name) || c.Name == name) &&
                                             (string.IsNullOrEmpty(lastname) || c.Lastname == lastname));
            // Return the query result
            return new ContactQueryResult()
            {
                Contacts = contacts,
                TotalCount = count
            };

        }
    }
}

 

ResourceReader.cs

using Flutnet.ServiceModel;

namespace FlutnetContacList.ServiceLibrary
{

    internal static class ResourceReader
    {
        // Read an image that is an embedded resource, using the resource name
        public static byte[] ReadByteResources(Type assemblyType, string name)
        {
            var assembly = assemblyType.Assembly;

            // Get the full resource name (in the assembly)
            string resourceName = assembly.GetManifestResourceNames()
                .SingleOrDefault(str => str.EndsWith(name));

            if (resourceName == null)
                return null;

            // Read the resource as byte stream
            using (Stream stream = assembly.GetManifestResourceStream(resourceName))
            {
                byte[] data = new byte[ stream.Length ];
                stream.Read(data, 0, (int)stream.Length);
                return data;
            }
        }
    }
}


Build ServiceLibrary project → $ flutnet pack 

By default the ServiceLibrary project is configured to execute the $ flutnet pack command in the POST BUILD event.

The procedure will generate all the required Flutter structure into the "flutnet_contact_list_bridge" project.

 

 


Configure the FlutnetBridge Mode

Based on your development preference you should use a specific FlutnetBridge mode:

The set the FlutnetBridge mode just edit the following files:

  • App.cs → ConfigureFlutnetBridge(FlutterEngine flutterEngine) on Xamarin Android,
  • ViewController.cs → ViewDidAppear(bool animated) on Xamarin iOS,
  • main.dart main() on Flutter.

Flutter

main.dart 

 

import 'package:flutter/material.dart';

import 'package:flutnet_contact_list_bridge/flutnet_bridge.dart';

 

void main() {

  // Configure the bridge mode for debug

  FlutnetBridgeConfig.mode = FlutnetBridgeMode.PlatformChannel;

  runApp(MyApp());

}

 

Xamarin Android

App.cs → ConfigureFlutnetBridge(FlutterEngine flutterEngine)

#if (DEBUG)
            _bridge = new FlutnetBridge(flutterEngine, this, FlutnetBridgeMode.PlatformChannel);
#else
            .....
#endif 

 

Xamarin iOS

ViewController.cs → ViewDidAppear(bool animated)

#if (DEBUG)
            _bridge = new FlutnetBridge(flutterEngine, this, FlutnetBridgeMode.PlatformChannel);
#else
            .....
#endif 

 

 

 


Develop your user interface in Flutter

This section will not cover all the aspect about the flutter app, but only the part reguarding the event management for the bloc, where we use our PlatformService to retreive the contacts informations.

 

The first thing to do is to add the Libraries needed by Flutter: open the pubspec.yaml and add this lines under "dependencies".

pubspec.yaml

....

dependencies:

  flutter:

    sdk: flutter

  flutter_bloc: ^3.2.0

  equatable: ^1.0.3

....

....

 

Before showing the events code, we just need to undestrand the basics about the Bloc Architecture.

The Flutnet PlatformService can be placed correctly between the Bloc  and Data layers.

The structure used in this example have designed the bloc to receive all the events from the UI, invoking the method applyAsync to each Event: this can be interpreted as a State Transition (for the bloc).

All the events will be implemented in the file contacts_page_events.dart.

contacts_page_events.dart

 

import 'dart:async';

import 'package:meta/meta.dart';

import 'package:flutnet_contact_list_bridge/contacts/index.dart';

// Index file will contain all the generated class reference ( like a multi-import )

import 'package:flutnet_contact_list_bridge/index.dart';

 

// Base event class for the Bloc

@immutable

abstract class ContactsPageEvent {

  @override

  String toString() => "ContactsPageEvent";

 

  Future<ContactsPageState> applyAsync( {ContactsPageState currentState, ContactsPageBloc bloc});

 

  // Use the same service id declared in Xamarin

  final ContactService _contactService = ContactService( "contact_service" );

}

 

// Load event: when the list load at the first time (empty)

class LoadContacts extends ContactsPageEvent {

  @override

  String toString() => "LoadContactsPageEvent";

 

  @override

  Future<ContactsPageState> applyAsync({

    ContactsPageState currentState,

    bloc,

  }) async {

    if (currentState is UnContactsPageState) {

      try {

        //* Add contact to the list

        ContactQueryResult result = await _contactService.queryContacts(

          limit: 12,

          startIndex: 0,

        );

        //* Check if all the contacts are obtained from Xamarin

        bool hasReachedMax = result.contacts.length >= result.totalCount;

 

        return InContactsPageState(

          hasReachedMax: hasReachedMax,

          contacts: result.contacts,

          isLoading: false,

        );

      } on Exception catch (_) {

        print("${this.toString()} + ${_?.toString()}");

        return ErrorContactsPageState(_?.toString());

      }

    }

    return currentState;

  }

}

// Fetch event: when the user scroll the list (the list is not empty and not all contact are loaded)

class FetchContacts extends ContactsPageEvent {

  @override

  String toString() => "FetchContacts";

 

  @override

  Future<ContactsPageState> applyAsync({

    ContactsPageState currentState,

    bloc,

  }) async {

    if (currentState is InContactsPageState) {

      try {

        //* Current contact loaded count

        int loadedCount = currentState?.contacts?.length ?? 0;

 

        //* Load more contacts querying the Xamarin service

        ContactQueryResult result = await _contactService.queryContacts(

          limit: 12,

          startIndex: loadedCount,

        );

 

        //* Join the retrieved contacts

        List<Contact> allLoadedContact =  currentState.contacts + ( result.contacts ?? []);

        //* Check if all the contacts are obtained from Xamarin

        bool hasReachedMax = allLoadedContact.length >= result.totalCount;

 

        return currentState.copyWith(

          hasReachedMax: hasReachedMax,

          contacts: allLoadedContact,

        );

      } catch (_) {

        print("${this.toString()} + ${_?.toString()}");

        return ErrorContactsPageState(_?.toString());

      }

    }

    return currentState;

  }

}

 

The contacts page widget will use the BlocBuilder to show different UI based on the Bloc State.

The resulting Flutter code will be like below.

contacts_page.dart

....

  // List scroll handler

  void _onScroll() {

    final maxScroll = _scrollController.position.maxScrollExtent;

    final currentScroll = _scrollController.position.pixels;

   // When the scroll reach the bottom list

    if (maxScroll - currentScroll <= _scrollThreshold &&

             !_scrollController.position.outOfRange) {

      _bloc.add(FetchContacts()); // Fetch more data

    }
  }
 

  @override

  void initState() {

    super.initState();

    _scrollController.addListener(_onScroll);

    _bloc.add(LoadContacts()); // Load the page

  }
 

  @override

  Widget build(BuildContext context) {

    return BlocBuilder(

      bloc: _bloc,

      builder: (BuildContext context, ContactsPageState state) {

        // Show the loading indicator when the page is not initialized

        if (state is UnContactsPageState) {

          return Center(child: CircularProgressIndicator());

        }

        // Display the error

        if (state is ErrorContactsPageState) {

          return Center(child: Text(state.errorMessage));

        }

        // The page is loaded

        if (state is InContactsPageState) {        

            // Draw the list of contacts

            List<Contact> contacts = state.contacts;

            // Here the list build....

        }

 

        return Text("App error!!!");

      },

    );

  }


Build the Flutter project

Defore running the Xamarin app, you need build your Flutter module project ("flutnet_contact_list" in our case).

To do so, you just open the terminal from visual studio code and run the command

  • $ flutter build aar --no-profile for FlutnetContactList.Android,
  • $ flutter build ios-framework --no-profile for FlutnetContactList.iOS ( ONLY on macOS ).

 

When the build finish you should be able to run your Xamarin app with Flutter as user interface.

 

 


Register services in the FlutnetRuntime

In order to expose the service to Flutter, you need to register it on the FlutnetRuntime, specifying by the registration key. 

The registration must be done before the FlutnetBridge start:

  • App→ ConfigureFlutnetRuntime() on Android
  • ViewController → ViewDidAppear(bool animated) on iOS

using the key "contact_service"

// Init runtime with the associate application-key (see app keys in the Flutnet Console)
FlutnetRuntime.Init("TRIAL-APP-KEY");

// Register the service
FlutnetRuntime.RegisterPlatformService(new ContactService(), "contact_service");

// Start the bridge .....