I tried Flutnet which can use Flutter in C # (Xamarin)
Flutter is great, but if I wish I could write it in C #, I’ve released a dream-like tool that makes it possible, so I tried it.
What is Flutnet
Flutnet is a framework that makes it easy to interoperate with Xamarin and Flutter. With Flutnet, you can build a beautiful UI with Flutter, but the logic part is not Dart, but the familiar C #. There is a charge, but there is a limited trial version that you can only use a unique application ID (bundle ID), so you can try it for free.
How to use
Flutnet has GUI tools that make it easy to build projects.
Installation
You can download the tool from here (https://www.flutnet.com/Download/Releases). To use it, you need to pass it through the path of the Flutter or Android SDK.
Setting example (macOS)
export PATH=$PATH:$HOME/development/flutter/bin:$HOME/Library/Android/sdk/platform-tools
If you haven’t built an environment such as Visual Studio or Flutter yet, click here for Windows (https://www.flutnet.com/Documentation/Getting-Started/Install-on-Windows), and for macOS. Please refer to here.
important point
Flutnet has a fixed version of Flutter that it supports. The version of the Flutnet.Interop.Android
and Flutnet.Interop.iOS
packages added to the created project is the supported version of Flutter, so be sure to install that version of Flutter. (Currently the latest (Flutnet 1.0.1 [BETA]) is 1.20.2
.)
Creating a project
Start the installed Flutnet program and click the [Next] and [Create] buttons to create the project.
It is created with the following folder structure.
MyApp
├── Flutter
│ ├── my_app
│ └── my_app_bridge
├── MyApp.Android
│ ├── Assets
│ ├── MainActivity.cs
│ ├── MainApplication.cs
│ ├── MyApp.Android.csproj
│ ├── Properties
│ └── Resources
├── MyApp.ModuleInterop.Android
│ ├── MyApp.ModuleInterop.Android.csproj
│ ├── Properties
│ └── Transforms
├── MyApp.PluginInterop.iOS
│ ├── ApiDefinitions.cs
│ ├── MyApp.PluginInterop.iOS.csproj
│ ├── Properties
│ └── StructsAndEnums.cs
├── MyApp.ServiceLibrary
│ ├── MyApp.ServiceLibrary.csproj
│ └── Service1.cs
├── MyApp.iOS
│ ├── AppDelegate.cs
│ ├── Assets.xcassets
│ ├── Entitlements.plist
│ ├── Info.plist
│ ├── LaunchScreen.storyboard
│ ├── Main.cs
│ ├── Main.storyboard
│ ├── MyApp.iOS.csproj
│ ├── Properties
│ ├── Resources
│ ├── SceneDelegate.cs
│ ├── ViewController.cs
│ └── ViewController.designer.cs
└── MyApp.sln
Below the Flutter
folder is the Flutter project, and the others are Xamarin projects. Open MyApp.sln
in Visual Studio and my_app
in Visual Studio Code or Android Studio with the Flutter plugin. my_app_bridge
contains Dart code that is automatically generated from C # code.
Build
The build must be done in both the Xmarin project and the Flutter project. For the time being, build MyApp.Android
or MyApp.iOS
that opened Visual Studio and try running it. If you see the default Flutter screen below, you are successful. If you don’t see it, your Flutter version may be different.
Next, build the Flutter project. Every time you change the code in Flutter, you need to build it. In the terminal, run the following command. (In the case of Visual Studio Code, get the flutter package in advance with Flutter: Get Packages
from the command palette)
Android
$ flutter build aar --no-profile
iOS
$ flutter build ios-framework --no-profile
In addition, you will need to rebuild the MyApp.ServiceLibrary
project on the Xamarin side for the changes to take effect in Xamarin. In addition, on Android, it seems that it will not be reflected unless you delete the previously installed application.
Use
A class called Service1
is created in the MyApp.ServiceLibrary
project. Let’s call this from Flutter.
Service1.cs
[PlatformService]
public class Service1
{
[PlatformOperation]
public string Hello(string name)
{
return $"Hi, {name}! How are you?";
}
}
The method with the PlatformOperation
attribute in the class with the PlatformService
attribute is the target to be called from Flutter. When you build the MyApp.ServiceLibrary
project, a Dart class to call this process is automatically created. In addition, you need to register an instance on each platform to call it. On Android, use ʻOnCreate () of
MainApplication, and on iOS, use
ViewDidLoad () of
ViewController`.
FlutnetRuntime.RegisterPlatformService(new Service1(), "my_service");
Since registration is for each plot form, it is also possible to add only the interface to the MyApp.ServiceLibrary
project and write and register the actual processing for each plot form.
Now let’s move on to the Flutter project and actually call it. The auto-generated classes are located at my_app_bridge / my_app / service_library / service_1.dart
. When creating an instance, set the name given in the second argument of RegisterPlatformService
.
final Service1 _service1 = new Service1('my_service');
The return value of the method is Future
, so use ʻawait` to get the value.
final message = await _service1.hello(name: name);
Below is the whole code.
main.dart
import 'package:flutter/material.dart';
import 'package:my_app_bridge/my_app/service_library/service_1.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final Service1 _service1 = new Service1('my_service');
String _message = '';
@override
void initState() {
super.initState();
_hello('Flutnet');
}
Future _hello(String name) async {
final message = await _service1.hello(name: name);
setState(() {
_message = message;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Text(
_message,
style: TextStyle(fontSize: 20),
),
));
}
}
In addition to method calls, it is also possible to define data classes that can be handled in common by Xamarin and Flutter, and to subscribe to events on the Xamarin side on the Flutter side.
Xamarin
[PlatformData]
public class Data
{
public int Value { get; set; }
}
[PlatformService]
public class EventService
{
[PlatformEvent]
public event EventHandler<ValueChangedEventArgs> ValueChanged;
}
Dart
final data = Data();
final value = data.value;
_eventService.valueChanged.listen((event) {
final value = event.value;
});
~~ However, in the current version (Flutnet.Android: 1.0.2, Flutnet.iOS: 1.0.2), when PlatformEvent is set, ʻArgumentNullException seems to occur at
RegisterPlatformService`. It’s probably a bug, so I think it will be fixed, but I’ll explain the workaround later. ~~
Fixed in 1.0.3.
Hot reload
Hot reload is also possible with Flutnet. After running in a Xamarin project, in the Flutter project, in Visual Studio Code, select Debug: Attach to Flutter process
from the command palette, and in Android Studio, select Flutter Attach from the Run menu. .. Once attached, it seems better to reboot once. That’s fine, but once I quit debugging and start again, the changes come back. This is because, as mentioned earlier, if you change the Flutter code, you will need to build again. Since it is a little troublesome to build each time, Flutnet has a mode to start two Xamarin apps that process logic and Flutter apps that check the screen at the same time and exchange data by local communication. It has been.
To switch to this mode (WebSocket mode), set as follows on Android, iOS, and Flutter.
MainApplication.cs
_bridge = new FlutnetBridge(flutterEngine, this, FlutnetBridgeMode.WebSocket);
ViewController.cs
_bridge = new FlutnetBridge(this.Engine, FlutnetBridgeMode.WebSocket);
main.dart
import 'package:my_app_bridge/flutnet_bridge.dart';
void main() {
FlutnetBridgeConfig.mode = FlutnetBridgeMode.WebSocket;
runApp(MyApp());
}
When debugging, after executing with Xamarin, Flutter also performs normal debugging execution. Since it is done by communication, it takes some time to acquire the data, but it saves the trouble of building each time, so this mode seems to be better when developing the screen layout.
About Platform Event
** After Flutnet 1.0.1 [BETA], the following measures are not required. ** **
As mentioned earlier, PlatformEvent cannot be set at this time. I’m getting a method using reflection internally, but it doesn’t seem to exist. It was probably okay in the debug build, but I think it was removed by the linker in the release build. What I’m doing is like registering an event, so I tried to force access and register by reflection. I think it’s okay because it worked with this.
MainApplication.cs
var eventService = new EventService();
var name = "event_service";
try
{
FlutnetRuntime.RegisterPlatformService(eventService, name);
}
catch
{
var request = typeof(FlutnetRuntime).GetField("request", BindingFlags.NonPublic | BindingFlags.Static).GetValue(null);
var creatorListener = request.GetType().GetProperty("Item", BindingFlags.Public | BindingFlags.Instance).GetValue(request, new[] { "" });
var interpreter = creatorListener.GetType().GetField("_Interpreter", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(creatorListener);
var utilsContainerAuth = interpreter.GetType().GetProperty("Item", BindingFlags.Public | BindingFlags.Instance).GetValue(interpreter, new[] { name });
var m_Service = utilsContainerAuth.GetType().GetField("m_Service", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(utilsContainerAuth);
var testPublisherMethod = typeof(FlutnetRuntime).GetMethod("TestPublisher", BindingFlags.NonPublic | BindingFlags.Static);
EventHandler<ValueChangedEventArgs> handler = (object sender, ValueChangedEventArgs e) => testPublisherMethod.Invoke(null, new[]
{
name,
nameof(EventService.ValueChanged),
sender,
e
});
eventService.ValueChanged += handler;
m_Service.GetType().GetProperty("Item", BindingFlags.Public | BindingFlags.Instance).SetValue(m_Service, handler, new[] { nameof(EventService.ValueChanged) });
}
ViewController.cs
var eventService = new EventService();
var name = "event_service";
try
{
FlutnetRuntime.RegisterPlatformService(eventService, name);
}
catch
{
var tag = typeof(FlutnetRuntime).GetField("tag", BindingFlags.NonPublic | BindingFlags.Static).GetValue(null);
var item = tag.GetType().GetProperty("Item", BindingFlags.Public | BindingFlags.Instance).GetValue(tag, new[] { "" });
var m_Getter = item.GetType().GetField("m_Getter", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(item);
var @base = m_Getter.GetType().GetProperty("Item", BindingFlags.Public | BindingFlags.Instance).GetValue(m_Getter, new[] { name });
var role = @base.GetType().GetField("_Role", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(@base);
var popMessageMethod = typeof(FlutnetRuntime).GetMethod("PopMessage", BindingFlags.NonPublic | BindingFlags.Static);
EventHandler<ValueChangedEventArgs> handler = (object sender, ValueChangedEventArgs e) => popMessageMethod.Invoke(null, new[]
{
name,
nameof(EventService.ValueChanged),
sender,
e
});
eventService.ValueChanged += handler;
role.GetType().GetProperty("Item", BindingFlags.Public | BindingFlags.Instance).SetValue(role, handler, new[] { nameof(EventService.ValueChanged) });
}
Summary
It’s just been released and it’s still volatile, but it has great potential. Especially when you try to recreate an application made with Xamarin.Forms with Flutter, the logic part can be used as it is, so it may be a great choice.