Can I call Kotlin function from Dart - android

Using Flutter, a kotlin/swift function can be called by something like:
file.dart:
static const platform = const MethodChannel('my.test.flutterapp/battery');
final int result = await platform.invokeMethod('getBatteryLevel');
file.kt:
private val CHANNEL = "my.test.flutterapp/battery"
MethodChannel(flutterView, CHANNEL).setMethodCallHandler { call, result ->
if (call.method == "getBatteryLevel") {
...
} else {
result.notImplemented()
}
}
Is there something similar to call Kotlin function from standard Dart app, like Dart console app!

https://flutter.io/developing-packages/
Plugin packages: A specialized Dart package which contain an API written in Dart code combined with a platform-specific implementation for Android (using Java or Kotlin), and/or for iOS (using ObjC or Swift). A concrete example is the battery plugin package.
...
flutter create --template=plugin -i swift -a kotlin hello

For the VM the mechanisms available are basic OS operations and native extensions.
By OS operations I mean, you could launch a separate process and interact with it, using files or the network stack. This is probably not as fine grained as you're looking for.
Native extensions allow you to call out to C or C++ code. I don't know enough about kotlin to know if you can easily expose functionality to C/C++. If it's possible, this will give you the tightest integration.
https://www.dartlang.org/articles/dart-vm/native-extensions

You can see this project: Full Sample
In Andriod:
class MainActivity: FlutterActivity() {
private val DATA_CHANNEL = "app.channel.shared.data"
override fun configureFlutterEngine(#NonNull flutterEngine: FlutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine);
MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), DATA_CHANNEL).setMethodCallHandler { call, result ->
if (call.method!!.contentEquals("getSharedText")) {
result.success("Shared Text")
}
}
}
}
In Dart:
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _MyAppState();
}
}
class _MyAppState extends State<MyApp> {
static const platform = const MethodChannel('app.channel.shared.data');
String dataShared = "No Data";
#override
void initState() {
super.initState();
getSharedText();
}
getSharedText() async {
var sharedData = await platform.invokeMethod("getSharedText");
if (sharedData != null) {
setState(() {
dataShared = sharedData;
});
}
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home:Scaffold(body: Center(child: Text(dataShared)))
);
}
}

Related

How to invoke method channel from Workmanager task

I have a method channel registered in the Android build's MainActivity class, which works fine when called from the foreground application.
I want to call this method channel from a task that Workmanager runs in the background, but I'm getting a MissingPluginException every time.
I'm guessing this doesn't work because a background task doesn't initialise the MainActivity, but I can't seem to find any information on how to register the channels in a place where the workmanager can call them.
My (simplified) setup is as follows:
lib/main.dart:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
Workmanager().initialize(callbackDispatcher, isInDebugMode: true);
Workmanager().registerPeriodicTask('uniquename', 'taskName',
frequency: const Duration(minutes: 15),
constraints: Constraints(networkType: NetworkType.connected));
runApp(const MyApp());
}
void callbackDispatcher() {
Workmanager().executeTask((taskName, inputData) async {
bool result = await DeviceInfoModel().getDeviceInfo();
return Future.value(result);
});
}
lib/device_info.dart:
class DeviceInfoModel extends ChangeNotifier {
static const platform = MethodChannel('deviceInfo');
Future<void> getDeviceInfo() async {
final int result = await platform.invokeMethod('getBatteryLevel');
// And other similar calls to other methods
}
}
android/app/src/main/kotlin/nl/myapp/MainActivity.kt:
package nl.myapp
class MainActivity : FlutterActivity() {
private val DEVICE_INFO_CHANNEL = "deviceInfo"
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
methodChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, DEVICE_INFO_CHANNEL)
methodChannel?.setMethodCallHandler { call, result ->
if (call.method == "getBatteryLevel") {
result.success(100)
}
}
}
}

how does one integrate Mixpanel into flutter applications?

i followed the steps given here
and after adding this
import 'package:mixpanel_flutter/mixpanel_flutter.dart';
...
class _YourClassState extends State<YourClass> {
Mixpanel mixpanel;
#override
void initState() {
super.initState();
initMixpanel();
}
Future<void> initMixpanel() async {
mixpanel = await Mixpanel.init("Your Mixpanel Token", optOutTrackingDefault: false);
}
...
the terminal is giving me an error saying
Field 'mixpanel' should be initialized because its type 'Mixpanel' doesn't allow null.
Mixpannel mixpannel;
failed to compile application
i have inserted the mixpannel token.
in Mixpannel, i started a test project added an event chart for all events.
Have you checked the example which is given by mixpanel-flutter
You can create the instance for it:
class MixpanelManager {
static Mixpanel? _instance;
static Future<Mixpanel> init() async {
if (_instance == null) {
_instance = await Mixpanel.init("YOUR_PROJECT_TOKEN",
optOutTrackingDefault: false);
}
return _instance!;
}
}
And you can use likewise (in other class):
late final Mixpanel _mixpanel;
#override
initState() {
super.initState();
_initMixpanel();
}
Future<void> _initMixpanel() async {
_mixpanel = await MixpanelManager.init();
}
Check out the example for more.

Flutter how to migrate this BLoC old version code to Bloc new version

how i must write this code in bloc v.8 i don know how i see some searches but im not understand and this is my code for classes they give me error => StateError (Bad state: add(DoFetchEvent) was called without a registered event handler.
Make sure to register a handler via on((event, emit) {...})):
class PostBloc extends Bloc<PostEvent, PostState> {
PostRepository repo;
PostBloc(PostState initialState, this.repo) : super(initialState);
Stream<PostState> mapEventToState(PostEvent event) async* {
if (event is DoFetchEvent) {
yield LoadingState();
try {
var posts = await repo.fetchPosts();
yield FetchSuccess(posts: posts);
} catch (e) {
yield ErrorState(message: e.toString());
}
}
}
}
import 'package:equatable/equatable.dart';
class PostEvent extends Equatable {
#override
List<Object?> get props => [];
}
class DoFetchEvent extends PostEvent {}
class PostState extends Equatable {
#override
List<Object?> get props => [];
}
class InitialState extends PostState {}
class LoadingState extends PostState {}
class FetchSuccess extends PostState {
List<PostModel> posts;
FetchSuccess({required this.posts});
}
class ErrorState extends PostState {
String message;
ErrorState({required this.message});
}
void main() {
runApp(MaterialApp(
home: BlocProvider(
create: (context) => PostBloc(InitialState(), PostRepository()),
child: MyApp(),
),
));
}
You can set your InitialState directly in the super constructor without manually passing it in like so.
PostBloc(this.repo) : super(InitialState()) {
on<DoFetchEvent>(_onDoFetchEvent);
}
Then you no longer pass in any state in the BlocProvider
BlocProvider<PostBloc>(
create: (BuildContext context) => PostBloc(PostRepository()),
...
Then your mapEventToState gets replaced with a method that takes the relevant event, and an Emitter<PostState> as arguments. yield then gets replaced with emit in the method.
Your whole class would look like this.
PostBloc(this.repo) : super(InitialState()) {
on<DoFetchEvent>(_onDoFetchEvent);
}
_onDoFetchEvent(
DoFetchEvent event,
Emitter<PostState> emit,
) async {
emit(LoadingState());
try {
var posts = await repo.fetchPosts();
emit(FetchSuccess(posts: posts));
} catch (e) {
emit(ErrorState(message: e.toString()));
}
}
}
That should do it.
Besides that, you're probably getting linter warnings about must_be_immutable on your state classes because PostState extend Equatable.
So I suggest making all PostState parameters final and adding the props override from Equatable to your state classes.
class ErrorState extends PostState {
final String message;
ErrorState({required this.message});
#override
List<Object?> get props => [message];
}

Integrating Rust + Flutter + Kotlin for Mobile Applications

As next week will have importat launch for Rust 2018 and Flutter 1.0, I thought to build an app using Rust for the business logic and Flutter for the user interface, that can run at both Android and iOS, I built one and tested it at Android and it is working fine.
I just wonder how to measure the performance and compare it with native Android/iOS app.
The app flow is:
Main is in Flutter, that is calling native function through platform_channel
The native function is calling rust library through JNI (JNI wrapper is required to be call the rust library)
The structure is as below:
The code used is:
main.dart:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.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> {
static const platform = const MethodChannel('samples.flutter.io/battery');
String _batteryLevel = 'Unknown battery level.';
Future<void> _getBatteryLevel() async {
String batteryLevel;
try {
final String hello = await platform.invokeMethod('getText');
final int result = await platform.invokeMethod('getBatteryLevel');
batteryLevel = '$hello Battery level at $result %.';
} on PlatformException catch (e) {
batteryLevel = "Failed to get battery level: '${e.message}'.";
}
setState(() {
_batteryLevel = batteryLevel;
});
}
#override
Widget build(BuildContext context) {
return Material(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
RaisedButton(
child: Text('Get Battery Level'),
onPressed: _getBatteryLevel,
),
Text(_batteryLevel),
],
),
),
);
}
}
JNI wrapper - RustGreetings.kt
package com.mozilla.greetings
class RustGreetings {
companion object {
init {
System.loadLibrary("greetings")
}
}
private external fun greeting(pattern: String): String
fun sayHello(to: String): String = greeting(to)
}
And the Main Android activity is:
package com.example.batterylevel
import android.os.Bundle
import io.flutter.app.FlutterActivity
import io.flutter.plugins.GeneratedPluginRegistrant
import io.flutter.plugin.common.MethodChannel
import android.content.Context
import android.content.ContextWrapper
import android.content.Intent
import android.content.IntentFilter
import android.os.BatteryManager
import android.os.Build.VERSION
import android.os.Build.VERSION_CODES
import lib.Library
import com.mozilla.greetings.RustGreetings
class MainActivity: FlutterActivity() {
private val CHANNEL = "samples.flutter.io/battery"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
GeneratedPluginRegistrant.registerWith(this)
MethodChannel(flutterView, CHANNEL).setMethodCallHandler { call, result ->
if (call.method == "getText") {
result.success(getText())
} else if (call.method == "getBatteryLevel") {
// result.success(getText())
val batteryLevel = getBatteryLevel()
if (batteryLevel != -1) {
result.success(batteryLevel)
} else {
result.error("UNAVAILABLE", "Battery level not available.", null)
}
}
else {
result.notImplemented()
}
}
}
private fun getBatteryLevel(): Int {
val batteryLevel: Int
if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
val batteryManager = getSystemService(Context.BATTERY_SERVICE) as BatteryManager
batteryLevel = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
} else {
val intent = ContextWrapper(applicationContext).registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
batteryLevel = intent!!.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) * 100 / intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1)
}
return batteryLevel
}
private fun getText(): String {
val x = Library().someLibraryMethod()
val g = RustGreetings()
val r = g.sayHello("My $x Rust")
return r
}
}
In the Android gradle.build I just added the below, as I'm interested to check also the impact of adding kotlin JVM library and getting it interacted with the Rust library within the mobile application:
dependencies {
implementation(files("src/main/libs/lib.jar"))
}
My question is:
How can check the performance and impact of each process when it is executed or called by another process
With the introduction of ffi in Dart, things became more smoother now, with a better performance as the interction now is Dart/Rust directly, without a need for Dart/Kotlin/Rust or Dart/Swift/Rust cycle, below a simple example:
First src/lib.rs
#[no_mangle]
pub extern fn rust_fn(x: i32) -> i32 {
println!("Hello from rust\nI'll return: {}", x.pow(2));
x.pow(2)
}
and Cargo.toml
[package]
name = "Double_in_Rost"
version = "0.1.0"
authors = ["Hasan Yousef"]
edition = "2018"
[lib]
name = "rust_lib"
crate-type = ["dylib"] # could be `staticlib` as well
[dependencies]
Running cargo build --release will generate target\release\rust_lib.dll copy/paste it into Dart application root directory
Write Dart code as below:
import 'dart:ffi';
import 'dart:io' show Platform;
// FFI signature of the hello_world C function
typedef ffi_func = Int32 Function(Int32 x); //pub extern fn rust_fn(x: i32) -> i32
// Dart type definition for calling the C foreign function
typedef dart_func = int Function(int x);
void main() {
// Open the dynamic library
var path = './rust_lib.so';
if (Platform.isMacOS) path = './rust_lib.dylib';
if (Platform.isWindows) path = 'rust_lib.dll';
final dylib = DynamicLibrary.open(path);
// Look up the Rust/C function
final my_func =
dylib.lookup<NativeFunction<ffi_func>>('rust_fn').asFunction<dart_func>();
print('Double of 3 is ${my_func(3)}');
}

How can I mock/stub out a Flutter platform channel/plugin?

I read the introduction to platform-specific plugins/channels on the Flutter website and I browsed some simple examples of a plugin, like url_launcher:
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'package:flutter/services.dart';
const _channel = const MethodChannel('plugins.flutter.io/url_launcher');
/// Parses the specified URL string and delegates handling of it to the
/// underlying platform.
///
/// The returned future completes with a [PlatformException] on invalid URLs and
/// schemes which cannot be handled, that is when [canLaunch] would complete
/// with false.
Future<Null> launch(String urlString) {
return _channel.invokeMethod(
'launch',
urlString,
);
}
In widgets tests or integration tests, how can I mock out or stub channels so I don't have to rely on the real device (running Android or iOS) say, actually launching a URL?
MethodChannel#setMockMethodCallHandler is deprecated and removed as of now.
Looks like this is the way to go now:
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
void mockUrlLauncher() {
const channel = MethodChannel('plugins.flutter.io/url_launcher');
handler(MethodCall methodCall) async {
if (methodCall.method == 'yourMethod') {
return 42;
}
return null;
}
TestWidgetsFlutterBinding.ensureInitialized();
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(channel, handler);
}
The details are on GitHub.
And here is a tested example for package_info plugin for future references:
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
void mockPackageInfo() {
const channel = MethodChannel('plugins.flutter.io/package_info');
handler(MethodCall methodCall) async {
if (methodCall.method == 'getAll') {
return <String, dynamic>{
'appName': 'myapp',
'packageName': 'com.mycompany.myapp',
'version': '0.0.1',
'buildNumber': '1'
};
}
return null;
}
TestWidgetsFlutterBinding.ensureInitialized();
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(channel, handler);
}
You can use setMockMethodCallHandler to register a mock handler for the underlying method channel:
https://docs.flutter.io/flutter/services/MethodChannel/setMockMethodCallHandler.html
final List<MethodCall> log = <MethodCall>[];
MethodChannel channel = const MethodChannel('plugins.flutter.io/url_launcher');
// Register the mock handler.
channel.setMockMethodCallHandler((MethodCall methodCall) async {
log.add(methodCall);
});
await launch("http://example.com/");
expect(log, equals(<MethodCall>[new MethodCall('launch', "http://example.com/")]));
// Unregister the mock handler.
channel.setMockMethodCallHandler(null);
When you create a plugin, you are automatically provided a default test:
void main() {
const MethodChannel channel = MethodChannel('my_plugin');
setUp(() {
channel.setMockMethodCallHandler((MethodCall methodCall) async {
return '42';
});
});
tearDown(() {
channel.setMockMethodCallHandler(null);
});
test('getPlatformVersion', () async {
expect(await MyPlugin.platformVersion, '42');
});
}
Let me add some notes about it:
Calling setMockMethodCallHandler allows you to bypass whatever the actual plugin does and return your own value.
You can differentiate methods using methodCall.method, which is a string of the called method name.
For plugin creators this is a way to verify the public API names, but it does not test the functionality of the API. You need to use integration tests for that.

Categories

Resources