How to test native Android method in Flutter App with FlutterDriver? - android

My Flutter app uses the native Android library.
That is, it uses the MethodChannel.
Now, I want to test the result of calling the native library's processing via the MethodChannel.
Since the MethodChannel requires calling Java processes, the test should be an Integration Test, not a Unit Test.
How can I run any method in Integration Test on my app?
Here is my test code.
// app.dart
void main() {
enableFlutterDriverExtension();
app.main();
}
// app_test.dart
void main() {
FlutterDriver driver;
group("MyTests", () {
setUpAll(() async {
driver = await FlutterDriver.connect();
});
tearDownAll(() async {
if (driver != null) {
driver.close();
}
});
test("Test1", () async {
var result1 = await CallNativeMethodWithMethodChannel.method1(value: -1);
expect(false, result1);
var result2 = await CallNativeMethodWithMethodChannel.method1(value: 0);
expect(false, result2);
var result3 = await CallNativeMethodWithMethodChannel.method1(value: 1);
expect(true, result3);
});
});
}
When this test code is executed, result1, result2, and result3 will always be null. This means that the method is not executed on the app.
How can I execute method1 on my app?
(Of course, I know that it is possible to test if I prepare a button on the app screen to run the test and tap it from the test code, but in reality, we have more than 100 items to test and it is not practical to prepare more than 100 buttons.)
Note :
I need to test the result of a native method call for a reason.
In other words, it is not appropriate to mock the execution result of a native method.
Also, I want to test a combination of Dart code and native methods, so I am not testing "only" the native methods.

Related

Flutter image_gallery_Saver plug-in cause UI thread gets stuck when saving pictures, and getting error when I try create it by compute

try {
//CheckPermission
if (await Permission.storage
.request()
.isGranted) {
final result = await SaveImageToKoi(post.fimgUrl!.url!);
print(result);
if (result != null)
BotToast.showText(text: "Success");
else
BotToast.showText(text: "Error");
}
} catch (e) {}
},
dynamic SaveImageToKoi (koi_url) async {
print(koi_url);
var response = await Dio().get(
koi_url,
options: Options(responseType: ResponseType.bytes));
final result = await ImageGallerySaver.saveImage(
Uint8List.fromList(response.data),
quality: 100);
}
I am trying to use the below library:
https://pub.dev/packages/image_gallery_saver
And when I am saving picture, it will cause the UI thread to get stuck. The stuck time depends on the size of the image
An error will be reported when trying to create a new thread by using compute to save. I don't know how to solve it, I just started learning Flutter and hope to receive
I guess the plug-in was written by Kotlin, and there was a problem when the interface communication was created by Dart
Thanks.
This is an error when I use compute
In platform_channal.dart
Thrown by
BinaryMessenger get binaryMessenger => _binaryMessenger ??
ServicesBinding.instance!.defaultBinaryMessenger;
exception = {_CastError} Null check operator used on a null value
Errors when using Compute
From your question it is not clear where your try/catch block code executes, but from what you describe I assume it is in a UI method such as build. If that is the case then indeed this code will block the UI, because while your statement final result = await SaveImageToKoi will yield to allow other methods to run, it won't proceed past this statement until the save is done.
You should perform the save function elsewhere (not blocking UI), and upon completion of the save trigger the UI to rebuild (typically by calling setState() in a Stateful Widget. For example, if you had a button in your UI that you want to trigger the save, then in your build function you might have this widget:
RaisedButton(
onPressed: () => {
SaveImageToKoi(post.fimgUrl!.url!).then((result) {
print(result);
setState(() {}); // this will trigger the UI redraw
});
},
child: new Text('Click me'),
),
The SaveImageToKoi statement here will not block, and upon completion of the save the code in the then argument is executed, with the result of the save operation. It is there that you call setState to trigger the UI refresh (if necessary - for example if you wanted to show the result in some way)

Upload to Firestore in background with Flutter

I am using christocracy's flutter_background_geolocation package to build a crowdsensing app. This app relies on the geofencing function of the aforementioned package quite heavily. In the main function, I have implemented a callback function that is as follows (partial code):
void _onGeofence(bg.GeofenceEvent event) async {
await showGeofenceNotification(flutterLocalNotificationsPlugin,
title: "Geofence", body: "$event", id: notification_id);
if (action == "ENTER") {
// update certain variables
BarometerService.startBarometerService();
BarometerService.floorChange.listen((floorChanges) {
// update floor
updateDatabase();
});
}
else if (action == "EXIT") {
// update certain variables
BarometerService.stopBarometerService();
}
updateDatabase();
setState(() {
// update UI
});
}
The code works perfectly when the app is open and in focus. However, when in background, the barometer service stops. The updateDatabase() function is also not carried out as my Firestore console doesn't get updated.
Here is the code for updating the database:
Future updateUserState(String matric, bool inLWN, bool inVaughan, String activity, int confidence, int floor) async {
return await userCollection.document(uid).setData({
'matric': matric,
'inLWN': inLWN,
'inVaughan': inVaughan,
'activity': activity,
'confidence': confidence,
'floor': floor,
});
}
And here is the code for BarometerService (which uses Flutter sensors plugin):
import 'package:sensors/sensors.dart';
static startBarometerService() {
Stream<BarometerEvent> barometer10Events = barometerEvents.throttle(Duration(seconds:PERIOD_SECONDS));
subscription = barometer10Events.listen(onBarometer);
streamController = new StreamController();
}
How do I make my services run even when app is closed or terminated? I have implemented the same code in my headless callback functions (except updating UI), but nothing besides updating my (local) variables and showing local flutter notifications is working.
Headless task for reference:
void headlessTask(bg.HeadlessEvent headlessEvent) async {
print('[BackgroundGeolocation HeadlessTask]: $headlessEvent');
switch(headlessEvent.name) {
case bg.Event.GEOFENCE:
bg.GeofenceEvent geofenceEvent = headlessEvent.event;
onHeadlessGeofence(geofenceEvent);
print('- [Headless] GeofenceEvent: $geofenceEvent');
break;
case bg.Event.ACTIVITYCHANGE:
bg.ActivityChangeEvent event = headlessEvent.event;
onHeadlessActivityChange(event);
print('- [Headless] ActivityChangeEvent: $event');
break;
}
}
onHeadlessGeofence is almost identical to the callback _onGeofence, besides the setState().
The full code can be found here

Run flutter code when android application class starts

I'm making a plugin for Flutter to handle fcm messages using an android native library.
As we know when a message is received by FCM, it starts the app (It's application class) and runs the codes within Application#onCreate block, so we can run native code when app starts by fcm in the background.
My question is, is it possible to run flutter code at that time when application starts?
For instance if the message was received:
Application class:
public class Application extends FlutterApplication {
#Override
public void onCreate() {
super.onCreate();
// Start flutter engine
// Invoke a dart code in the Plugin using methodChannel or etc.
}
}
Short answer, Yes
You can call a Dart method in background using it's handle key.
1. Register your plugin in the background
Implement a custom application class (override FlutterApplication)
public class MyApp extends FlutterApplication implements PluginRegistry.PluginRegistrantCallback {
#Override
public void registerWith(io.flutter.plugin.common.PluginRegistry registry) {
// For apps using FlutterEmbedding v1
GeneratedPluginRegistrant.registerWith(registry);
// App with V2 will initialize plugins automatically, you might need to register your own however
}
}
Remember to register the class in the AndroidManifest by adding android:name=".MyApp" to <application> attributes.
What is embedding v2?
2. Create a setup function as top level function in your flutter code
/// Define this TopLevel or static
void _setup() async {
MethodChannel backgroundChannel = const MethodChannel('flutter_background');
// Setup Flutter state needed for MethodChannels.
WidgetsFlutterBinding.ensureInitialized();
// This is where the magic happens and we handle background events from the
// native portion of the plugin.
backgroundChannel.setMethodCallHandler((MethodCall call) async {
if (call.method == 'handleBackgroundMessage') {
final CallbackHandle handle =
CallbackHandle.fromRawHandle(call.arguments['handle']);
final Function handlerFunction =
PluginUtilities.getCallbackFromHandle(handle);
try {
var dataArg = call.arguments['message'];
if (dataArg == null) {
print('Data received from callback is null');
return;
}
await handlerFunction(dataArg);
} catch (e) {
print('Unable to handle incoming background message.\n$e');
}
}
return Future.value();
});
3. Create a top level callback that will get the background message and calls it
_bgFunction(dynamic message) {
// Message received in background
// Remember, this will be a different isolate. So, no widgets
}
4. Get the handle key of the background function and setup and send it to native via MethodChannel
// dart:ui needed
CallbackHandle setup PluginUtilities.getCallbackHandle(_setup);
CallbackHandle handle PluginUtilities.getCallbackHandle(_bgFunction);
_channel.invokeMethod<bool>(
'handleFunction',
<String, dynamic>{
'handle': handle.toRawHandle(),
'setup': setup.toRawHandle()
},
);
5. Save them into SharedPref in the native side
public void onMethodCall(MethodCall call, MethodChannel.Result result) {
String methodName = call.method
if (methodName == "handleFunction") {
long handle = call.argument("handle");
long setup = call.argument("setup");
// save them
}
}
6. When background is awaken, start a background isolate
FlutterMain.ensureInitializationComplete(context, null)
val appBundlePath = FlutterMain.findAppBundlePath()
val flutterCallback = FlutterCallbackInformation.lookupCallbackInformation(setupHandleYouHadSaved)
FlutterNativeView backgroundFlutterView = FlutterNativeView(context, true)
val args = FlutterRunArguments()
args.bundlePath = appBundlePath
args.entrypoint = flutterCallback.callbackName
args.libraryPath = flutterCallback.callbackLibraryPath
backgroundFlutterView?.runFromBundle(args)
// Initialize your registrant in the app class
pluginRegistrantCallback?.registerWith(backgroundFlutterView?.pluginRegistry)
7. When your plugin is registered, create a background channel and pass it to
val backgroundChannel = MethodChannel(messenger, "pushe_flutter_background")
8. Call the setup method that would call and give the message to you callback
private fun sendBackgroundMessageToExecute(context: Context, message: String) {
if (backgroundChannel == null) {
return
}
val args: MutableMap<String, Any?> = HashMap()
if (backgroundMessageHandle == null) {
backgroundMessageHandle = getMessageHandle(context)
}
args["handle"] = backgroundMessageHandle
args["message"] = message
// The created background channel at step 7
backgroundChannel?.invokeMethod("handleBackgroundMessage", args, null)
}
The sendBackgroundMessageToExecute will execute the dart _setup function and pass the message and callback handle. In the step 2, callback will be called.
Note: There are still certain corner cases you may want to consider (for instance thread waiting and ...). Checkout the samples and see the source code.
There are several projects which support background execution when app is started in the background.
FirebaseMessaging
Pushe
WorkManager
I did it a different, simpler way compared to Mahdi's answer. I avoided defining an additional entrypoint/ callback, using PluginUtilities, callback handles, saving handles in SharedPreferences, passing messages with handles between dart and platform, or implementing a FlutterApplication.
I was working on a flutter plugin (so you don't have to worry about this if you use my library for push notifications 😂), so I implement FlutterPlugin. If I want to do background processing and the Flutter app isn't running, I just launch the Flutter app without an Activity or View. This is only necessary on Android, since the FlutterEngine/ main dart function runs already runs when a background message is received in an iOS app. The benefit is that this is the same behaviour as iOS: a Flutter app is always running when the app is launched, even if there is no app shown to the user.
I launch the application by using:
flutterEngine = new FlutterEngine(context, null);
DartExecutor executor = flutterEngine.getDartExecutor();
backgroundMethodChannel = new MethodChannel(executor, "com.example.package.background");
backgroundMethodChannel.setMethodCallHandler(this);
// Get and launch the users app isolate manually:
executor.executeDartEntrypoint(DartExecutor.DartEntrypoint.createDefault());
I did this to implement background push notification handling in a library, ably_flutter. It seems to work well. The FlutterEngine/ application is launched only when the application is not already running. I do this by keeping track of the activity (using ActivityAware):
if (isApplicationInForeground) {
// Send message to Dart side app already running
Intent onMessageReceivedIntent = new Intent(PUSH_ON_MESSAGE_RECEIVED);
onMessageReceivedIntent.putExtras(intent.getExtras());
LocalBroadcastManager.getInstance(context).sendBroadcast(onMessageReceivedIntent);
} else if (AblyFlutterPlugin.isActivityRunning) {
// Flutter is already running, just send a background message to it.
Intent onMessageReceivedIntent = new Intent(PUSH_ON_BACKGROUND_MESSAGE_RECEIVED);
onMessageReceivedIntent.putExtras(intent.getExtras());
LocalBroadcastManager.getInstance(context).sendBroadcast(onMessageReceivedIntent);
} else {
// No existing Flutter Activity is running, create a FlutterEngine and pass it the RemoteMessage
new PushBackgroundIsolateRunner(context, asyncCompletionHandlerPendingResult, message);
}
Then, I just use a separate MethodChannel to pass the messages back to the dart side. There's more to this parallel processing (like telling the Java side that the App is running/ ready. Search for call.method.equals(pushSetOnBackgroundMessage) in the codebase.). You can see more about the implementation PushBackgroundIsolateRunner.java at ably_flutter. I also used goAsync inside the broadcast receiver to extend the execution time from 10s to 30s, to be consistent with iOS 30s wall clock time.
You can use a headless Runner to run dart code from an Application class (or service, broadcast receiver etc).
There's a good in depth article on how to implement this: https://medium.com/flutter/executing-dart-in-the-background-with-flutter-plugins-and-geofencing-2b3e40a1a124
According to my knowledge we have to call a class GeneratedPluginRegistrant.registerWith(this); at oncreate method where flutter code has to run.
If you mean you want to run some arbitrary Dart code in the background you can use the this plugin we created which really facilitates the use of background work.
You can register a background job that should be executed at a given point in time and it will call back in to your Dart code where you can run some code in the background.
//Provide a top level function or static function.
//This function will be called by Android and will return the value you provided when you registered the task.
//See below
void callbackDispatcher() {
Workmanager.defaultCallbackDispatcher((echoValue) {
print("Native echoed: $echoValue");
return Future.value(true);
});
}
Workmanager.initialize(callbackDispatcher)
Then you can schedule them.
Workmanager.registerOneOffTask(
"1",
"simpleTask"
);
The String simpleTask will be returned in the callbackDispatcher function once it starts running in the background.
This allows for you to schedule multiple background jobs and identify them by this id.

How ErrorUtils work with android for uncaught exception in React Native on debug and production mode?

I want to catch uncaught exception of android react native app. For this some blogs suggesting to use following code:
ErrorUtils.setGlobalHandler(error => {
sentry.capture(error);
NativeModules.BridgeReloader.reload()
});
But I don't know the complete mechanism to implement this. Can any one let me know how do I implement code for such requirement?
I got solution, We should use ErrorUtils handler to detect run time exception. For this we have to add
if (ErrorUtils._globalHandler) {
instance.defaultHandler = ErrorUtils.getGlobalHandler && ErrorUtils.getGlobalHandler() || ErrorUtils._globalHandler;
ErrorUtils.setGlobalHandler(this.wrapGlobalHandler); //feed errors directly to our wrapGlobalHandler function
}
and in your wrapGlobalHandler method
async wrapGlobalHandler(error, isFatal) {
//* Report error here
setTimeout (() => {
this.defaultHandler(error, isFatal); //after you're finished, call the defaultHandler so that react-native also gets the error
if (Platform.OS == 'android') {
NodeModule.reload()
}
}, 1000);
}

Android, Xamarin Forms PCL, PortableRest PCL and Async Web Api Call

I am trying to use PortableRest to make an Async call to a Web API 2.2 Rest service from Xamarin Forms.
I think I have some kind of deadlock / synchronisationcontext issue but I cannot work it out (newbie to async etc).
Can anyone please help?
My controller test method (removed any call to database) -
public IEnumerable<ContentModel> GetTestRest()
{
return new List<ContentModel> {
new ContentModel() {Categoryid = 1, Title = "Title"}};
}
My Unit Test Passes -
[TestMethod]
public async Task TestRest()
{
MyNewsApiClient MyNewsApiClient = new MyNewsApiClient();
var models = await MyNewsApiClient.TestRest();
int count = models.Count;
Assert.AreEqual(1, count);
}
My PortableRest Proxy (PCL) Method -
public async Task<List<ContentModel>> TestRest()
{
var request = new RestRequest();
request.Resource = "Content/GetTestRest";
return await ExecuteAsync<List<ContentModel>>(request);
}
Xamarin Forms ContentPage (PCL) -
public partial class NewsType1CP : ContentPage
{
public NewsType1CP ()
{
InitializeComponent ();
}
protected override void OnAppearing ()
{
LoadData (); // this is a sync call of an async method, not sure how else to approach, make OnAppearing async?
}
public async Task LoadData ()
{
Debug.WriteLine ("LoadData");
HeaderLabel.Text = "Load Data!";
MyNewsApiClient api = new MyNewsApiClient ();
var cm = await api.TestRest ();
// this will work on its own, control returns to this method - await api.GetDelay ();
HeaderLabel.Text = "After! - ";
NewsListView.ItemsSource = cm;
}
}
The await to api.TestRest() never results in HeaderLabel.After or ListView being set.
If I just add a test Proxy Method GetDelay() which does not call PortableRest via return await ExecuteAsync>(request);
public async Task<bool> GetDelay ()
{
await Task.Delay (1000);
return true;
}
Then all "works".
Thanks for your help, I will download the source code for PortableRest if required (using Nuget dll at present), just was not sure if missing something basic at this top level.
Right, this eventually has nothing to do with Async (or any Issue with PortableRest), but thought I would share in case it can help others.
I eventually put a try block (obvious next step now!) and caught this exception -
Method not found: 'System.Net.Http.HttpClientHandler.set_AutomaticDecompression'.
So, looking at this -
http://davidburela.wordpress.com/2013/07/12/error-when-using-http-portable-class-library-compression/
PortableRest was already in my Forms PCL, but I needed to add Microsoft Http Client Libraries Nuget to my Android Application.
I then got -
Error: ConnectFailure (The requested address is not valid in this context)
I thought it may be something to do with Android Permissions, but it was not in this case (although I now realise I need to add Internet permission anyway for future calls).
Because I am running my App in a VM (from GenyMotion / Virtual Box) then the Android App cannot access LocalHost (where my Web Api resides)
So, from this -
http://bbowden.tumblr.com/post/58650831283/accessing-a-localhost-server-from-the-genymotion
I can confirm that using the address shown in VirtualBox > File > Preferences > Network > VirtualBox Host-Only Network Adapter > Modify does the job beautifully.
Thanks for your help.

Categories

Resources