Write your first Flutnet App

If you don't have already installed Flutnet on your machine just follow the instraction here in Install Flutnet on your machine.

 

In this section we are going to understand all the processes to develop a Flutnet application:

  1. Creating The Project
  2. Defining the PlatformService and Registering it on the FlutnetRuntime
  3. Building ServiceLibrary Project ( $ flutnet pack )
  4. Buildind the Flutter Project
  5. Configuring the FlutnetBridge
  6. Implement Homepage Widget (in Flutter)

 

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


What we want to obtain?

In this example we want to display the "My First App" string in a Flutter page:

The string value will be obtained from Xamarin implementing a specific PlatformService, that we will call MyFirstService.

 

This service will expose a simple method that provides the string "My First App", so that Flutter can request it.

 

     

 


Create the project

Open the Flutnet Console, and press "New Project".

We set our first project  name as "MyFirstApp" and leave the settings as default. If you are developing in Windows system remember that Flutter module cannot be built. Only on mac OS you can develop iOS Flutter apps.

 

The next section of the GUI will let you choose specific projects names and allow to group all the Dart projects together in a single folder.

The creation process could be taking sometime, so just wait until the procedure ends.

 

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

  • Xamarin
    • ​MyFirstApp.Android
    • MyFirstApp.iOS
    • MyFirstApp.ServiceLibrary (net standard)
  • Flutter
    • ​my_first_app (flutter module, where you develop the UI)
    • my_first_app_bridge (bridge package, genetared by the $ flutnet pack command)

 

Now that we have the basic template project, we can start to understand how to realize our first Flutnet Application.


Define your PlatformService

The first thing we need to do is declaring our Service: we can just create the class MyFirstService.cs in the MyFirstApp.ServiceLibrary project, using the PlatformService attribute.

This service will expose a public method called GetMessage, that will return the "My First App" string.

 

MyFirstService.cs

using Flutnet.ServiceModel;

namespace MyFirstApp.ServiceLibrary
{

    // This service class will be exposed to Flutter
    [PlatformService]
    public class MyFirstService
    {

        // This operation will be exposed to Flutter
        [PlatformOperation]
        public string GetMessage()
        {
            return "My First App";
        }
    }
}

 

In this example the service will be ambivalent for both Android and iOS, because we have implemented the logic inside the shared ServiceLibrary project (net standard).

 


Register "MyFirstService" on the FlutnetRuntime

In order to have a fully functional PlatformService on your application, you need to register an istance of it on the FlutnetRuntime, so Flutter can access the specific instace, identified by the registration key. 

The registration must be done before the FlutnetBridge start:

  • App → ConfigureFlutnetBridge(FlutterEngine flutterEngine) on Android
  • ViewController → ViewDidAppear(bool animated) on iOS.

This example is very simple and require only a sigle instace of the class: we use the key "my_first_service" to register the instace. 

MyFirstApp.Android

App.cs

using Flutnet;
using Flutnet.Interop.Embedding.Engine;
using Environment = System.Environment;
using MyFirstApp.ServiceLibrary;

namespace MyFirstApp
{
    /// Singleton class for Application wide objects. 
    public static class App
    {

        //...    
        private static void ConfigureFlutnetRuntime()
        {
            try
            {
                //
                // Init the runtime using the application key (generated from the flutnet console)
                //

                FlutnetRuntime.Init("TRIAL-APP-KEY");
                
                // 
                // Register the service on the flutnet runtime
                //  

                FlutnetRuntime.RegisterPlatformService(new MyFirstService(), "my_first_service");
                
                Initialized = true;
            }
            catch (Exception e)
            {
                // handle init errors
            }
        }

        // ....
    }

}

 

 

MyFirstApp.iOS

ViewController.cs

using System;
using UIKit;
using Flutnet;
using Flutnet.Interop;
using MyFirstApp.ServiceLibrary;
using System.Threading.Tasks;

namespace MyFirstApp
{
    public partial class ViewController : FlutterViewController
    {
        
        // ........

        public override async void ViewDidAppear(bool animated)
        {
            base.ViewDidAppear(animated);

           //...

            try
            {
                //
                // Init the runtime using the application key (generated from the flutnet console)
                //

                FlutnetRuntime.Init("TRIAL-APP-KEY");
                
                // 
                // Register the service on the flutnet runtime
                // 

                FlutnetRuntime.RegisterPlatformService(new MyFirstService(), "my_first_service");

                // Initialize the bridge
                //....
            }
            catch (Exception e)
            {
                // handle init errors
            }
        }
    }
}

 

 


Build your ServiceLibrary project (flutnet pack)

By default the *.ServiceLibrary project (MyFirstApp.ServiceLibrary in our case), is configured to execute the $ flutnet pack command in the POST BUILD event. The procedure will generate all the required Flutter structure into the "my_first_app_bridge" project.

You need to rebuild the *.ServiceLibrary project every time you made some changes about PlatformService, PlatformOperation, PlatformEvent and PlatformData. Otherwise you will not see all the new classes and methods inside your flutter project.

 

 


Open "my_first_app" using VS Code

With our generated and updated "my_first_app_bridge" project, we can have access to all the Service Operation that we have implemented in Xamarin (MyFirstService in our case).

We can open the "my_first_app" project using Visual Studio Code.

When the project has loaded, we need to refresh all the flutter packages in order to see all the generated code present in the "my_first_app_bridge" project: from Visual Studio Code press:

F1 → "Flutter: Get Packages" (from the drop down menu)

 

To verify that our "my_first_app_bridge" project is loaded correctly you only need to check the the bottom left panel "DEPENDENCIES" in the IDE.

 

From the DEPENDENCIES panel you can navigate through the "my_first_app_bridge" package, where you find all your Xamarin exported classes. Use the package path to import the class you need.

 

 


Build "my_first_app" project

In order to run your Xamarin app, you need to build at least one time the Flutter module project ("my_first_app" 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 MyFirstApp.Android,

$ flutter build ios-framework --no-profile for MyFirstApp.iOS ( ONLY on macOS ).

 

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

 


Configure the FlutnetBridge mode

Based on your development preferences you should use a specific FlutnetBridge mode.

In this example we will use the WebSocket mode by adding this line of code in the main.dart file. This configuration allow to develop Xamarin and Flutter in complete isolation.

 

MyFirstApp.Android

App.cs

// ...imports

namespace MyFirstApp
{

    public static class App
    {

        // ...

        public static void ConfigureFlutnetBridge(FlutterEngine engine)
        {
            try
            {
#if DEBUG
                _bridge = new FlutnetBridge(engine, AppCtx, FlutnetBridgeMode.WebSocket);
#else
                _bridge = new FlutnetBridge(engine, AppContext);
#endif
            }
            catch (Exception e)
            {
                // Handle bridge errors
            }
        }

        // ....
    }

}

 

MyFirstApp.iOS

ViewController ​.cs

// ...imports

namespace MyFirstApp
{

    public class ViewController : FlutterViewController
    {

        // ...

        public override async void ViewDidAppear(bool animated)
        {

            base.ViewDidAppear(animated);


            try
            {

                 // Init the Flutnet Runtime and Services ....


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

               _initialized = true;
            }
            catch (Exception e)
            {
                // Handle bridge errors
            }
        }

        // ....
    }

}

 

my_first_app

main.dart

 

// Import MyFirstService class from my_first_app_bridge package: the namespace is converted in "Dart format"

import 'package:my_first_app_bridge/my_first_app/service_library/my_first_service.dart';

// Import FlutnetBridge class (auto-generated)

import 'package:my_first_app_bridge/flutnet_bridge.dart';

 

void main() {

  // Configure the bridge mode

  FlutnetBridgeConfig.mode = FlutnetBridgeMode.WebSocket;

  runApp(MyApp());

}

 

 

We chose WebSocket mode to gain  the full benefit about the hot-reload feature from Flutter.

 

WebSocket vs PlatformChannel

Hot-reload will work even using PlatformChannel mode, with the difference that all the changes made in Flutter (during the debug session) will be lost when the Xamarin app restart (you should rebuild the project to apply your change permanently in the Xamarin app: this is slow). 

Instead, using WebSocket mode, the two application "parts" (Xamarin and Flutter) can be developed fully separated, so this last inconvenient will not persists.

 

 

Now you can run both Xamarin (Android / iOS) and Flutter project simultaneously: follow the guide Debugging in WebSocket Configuration to start your project.

 

 


Implement "MyHomePage" Widget

 

With my_first_app project "ready to go", we analyze what Flutter need to do:

  1. We need a page that show a string message

  2. We need to obtain the message from our MyFirstService

In Flutter world, everything is a Widget. Basically a widget is a UI component, that can hold or not informations. If the widget need to hold some information, will be saved in its State. If a widget need to maintain some information will be a StatefullWidget, otherwise a StatelessWidget.

 

In this example we are going to create a page that hold the message obtained from Xamarin: until the message is not obtaint from Xamarin, the value will be "...loading" (just for seeing something on screen). When the MyFirstService call is finished, the real value will be update on the Flutter page (using setState() method).

 

The important part is that the service is initialized using the registration key "my_first_service" (the same used in Xamarin → FlutnetRuntime).

 

main.dart  (MyHomePage class)

 

class MyHomePage extends StatefulWidget {

  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override

  _MyHomePageState createState() => _MyHomePageState();

}

 

class _MyHomePageState extends State<MyHomePage> {

  // Reference to the xamarin welcome service

  final MyFirstService _myFirstService = new MyFirstService("my_first_service");

 

  // Initial message value

  String _messageValue "....loading";

 

  // Load the message value from Xamarin

  void _load() async {

    try {

      String tmp = await _myFirstService.getMessage();

     // rebuild the widget with the new message value (should be "My First App")

      setState(() {

        _messageValue = tmp;

      });

    } on Exception catch (ex) {

      setState(() {

        _messageValue = "$ex";

      });

    }

  }

 

  // When the state is started, we call the _load() funtion

  @override

  void initState() {

    super.initState();

    _load();

  }

}

 

The build method will declare what UI component we want to view.  

 @override

  Widget build(BuildContext context) {

    return Scaffold(

      appBar: AppBar(title: Text(widget.title)),

      body: Center(

        child: Text(

          _messageValue ,

          style: TextStyle(

            fontSize: 20,

          ),

        ),

      ),

    );

  }

                        

 

 

The final result will be as follow:

When the application
start

After loading the value
from Xamarin