Flutter force to release foreground - android

How to release foreground to the previous application in Flutter (Dart) ? I mean, how to force to call the onPause or viewWillDisappear to let my app disappear and let the previous app come back to the foreground.
Is there a method thant I can call ?
Edit : I don't wan't to close my app, juste "minimize" it.

You are struggling with a mismatch between Flutter's architecture and Android's. In your previous question you needed a way to bring your flutter app to the foreground, to which the answer is a full-screen intent notification. The problem is that in native Android, you would probably have used the NEW_TASK flag to start a new task. As Flutter only has one activity, it's necessary to use USE_CURRENT instead.
With NEW_TASK, you would use Activity.finish() to close it, closing just the new activity. If you did that with Flutter, that would probably close the whole app (because of the use of USE_CURRENT).
It might be possible to have a native Android app (allowing you to have more direct control of the launch of activities) and to use Add2App to add the Flutter screen(s). If you get that to work, I'd like to know.

I finally got a solution ! I haven't found yet a solution for the IOS side : I'm working on it.
I used MethodChannel to ask to the native side to minimize itself. For Android use this.moveTaskToBack(true); ! If you got an Objectif-C alternative, it will be perfect !
Dart:
class _MyHomePageState extends State<MyHomePage> {
static const MethodChannel actionChannel = MethodChannel('method.channel');
Future<void> _minimize() async{
try{
await actionChannel.invokeMethod('minimize');
} on PlatformException catch(e) {
print('${e.message}');
}
}
}
Android:
public class MainActivity extends FlutterActivity {
private static final String ACTION_CHANNEL = "method.channel";
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Action-post-alert method
new MethodChannel(getFlutterView(), ACTION_CHANNEL).setMethodCallHandler(
new MethodCallHandler() {
#Override
public void onMethodCall(MethodCall call, Result result) {
if (call.method.equals("minimize")) {
this.moveTaskToBack(true);
result.success(true);
} else {
result.notImplemented();
}
}
}
);
}
}

Related

Navigate to a specific route in MAUI using Shell, from OnNewIntent in MainActivity?

I decided to experiment with MAUI. I am approaching first an Android App, and using Shell for navigation.
My App has 2 ways of opening:
When it's opened by the user tapping on the icon
Through a deep link, triggered by another app.
The issue I'm having is that when the app is triggered through the Deep link, I need to navigate to a specific page. I am trying to do it on the OnNewIntent accessing the Current instance of Shell, but when doing GoToAsync("my_route") it gives an error when trying to navigate to the new page.
This is what I have on my MainActivity:
protected override void OnNewIntent(Intent intent)
{
base.OnNewIntent(intent);
var action = intent.Action;
var data = intent.DataString;
if (!string.IsNullOrWhiteSpace(data) && data.Contains("/data/")) {
if(Shell.Current != null)
{
Shell.Current.GoToAsync("myroute)";
// Also tried:
// - Shell.Current.GoToAsync("myroute").Wait();
// - App.Current.Dispatcher.Dispatch(async () => await Shell.Current.GoToAsync("//myroute")); (suggested by #toolmakersteve )
}
}
}
And this is the error:
Java.Lang.IllegalArgumentException: 'No view found for id 0x1
(unknown) for fragment ShellItemRenderer{19d353d}
(6c8560ab-dd58-4cbf-9e8b-2b9e12315f45 id=0x1)'
I'm assuming this has something to do with the fact that what I'm doing is not possible, so I need to find the RIGHT way to navigate to a specific page from OnNewIntent on MAUI, using Shell navigation.
UPDATE: It's also important to note that when the Deep Link triggers the app to open, there are two different behaviours:
If the app was already running, it throws the above mentioned exception
If the app was not already running, it opens regularly on the main screen, with no errors, but I would expect it to navigate to the desired Page.
Thanks!
First, make sure that GoToAsync("myroute") works if you use it somewhere more typical, such as a button press.
Assuming that works, then perhaps the intent code isn't running in the Dispatcher context (previously known as MainThread). Try:
Dispatcher.Dispatch(() => {
Shell.Current.GoToAsync("myroute");
});
VERSION 2
Perhaps deep link logic runs BEFORE App's OnResume.
If so, this might work:
In App.xaml.cs:
public partial class App : Application
{
...
public static bool FromDeepLink;
protected override void OnResume()
{
base.OnResume();
if (FromDeepLink)
{
FromDeepLink = false;
MainPage = new MainPage();
Dispatcher.Dispatch(() =>
{
Shell.Current.GoToAsync("myroute");
});
}
}
}
Then in OnNewIntent:
if (!string.IsNullOrWhiteSpace(data) && data.Contains("/data/")) {
App.FromDeepLink = true;
}
Conceptually #ToolmakerSteve answer is correct, but the OnResume event of the Application class seems not to fire when the app is resumed by intent (seems to be a Maui bug), however Android's native OnResume works and fires correctly even when the app is resumed via intent, all you have to do is in the MainActivity class to override Android's native OnResume method:
protected override void OnResume()
{
base.OnResume();
var fromDeepLink = Preferences.Get("FromDeepLink", false);
if (fromDeepLink)
{
Preferences.Set("FromDeepLink", false);
Shell.Current.GoToAsync("myroute");
}
}
protected override void OnNewIntent(Intent intent)
{
base.OnNewIntent(intent);
var action = intent.Action;
var data = intent.DataString;
if (!string.IsNullOrWhiteSpace(data) && data.Contains("/data/"))
{
Preferences.Set("FromDeepLink", true);
}
}

Scan and listen to events from Bluetooth devices in background with flutter

I want to set up a mobile application with flutter which also runs in the background. this application allows you to scan Bluetooth devices and listen to events to launch notification and/or start a ringtone.
I managed to do all this and it works very well with the flutter_blue plugin. But my problem is that the application has to keep running in the background.
I came here to seek help.
The app does exactly what this app does https://play.google.com/store/apps/details?id=com.antilost.app3&hl=fr&gl=US
There are 2 ways to do it.
All you have to do that is write a native code in JAVA/Kotlin for android and obc-c/swift for ios.
The best place to start with this is here
If you just follow the above link then you will be able to code MethodChannel and EventChannel, which will be useful to communicate between flutter and native code. So, If you are good at the native side then it won't be big deal for you.
// For example, if you want to start service in android
// we write
//rest of the activity code
onCreate(){
startBluetoothService();
}
startBluetoothService(){
//your code
}
//then, For the flutter
// Flutter side
MessageChannel msgChannel=MessageChannel("MyChannel");
msgChannel.invokeMethode("startBluetoothService");
// Native side
public class MainActivity extends FlutterActivity {
private static final String CHANNEL = "MyChannel";
#Override
public void configureFlutterEngine(#NonNull FlutterEngine flutterEngine) {
super.configureFlutterEngine(flutterEngine);
new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL)
.setMethodCallHandler(
(call, result) -> {
if (call.method.equals("startBluetoothService")) {
int response = startBluetoothService();
//then you can return the result based on the your code execution
if (response != -1) {
result.success(response);
} else {
result.error("UNAVAILABLE", "Error while starting service.", null);
}
} else {
result.notImplemented();
}
}
);
}
}
same as above you can write the code for the iOS side.
Second way is to write your own plugin for that you can take inspiration from alarm_manager or Background_location plugins.
I hope it helps you to solve the problem.

FlutterDriver: Use Android back button

I have a mobile application for Android with multiple pages and currently try to write simple integration tests for it...
The issue is that my app only uses internal Android back gestures, I have no back button or something like this.
Is it somehow possible to tell the FlutterDriver to go one page back? (Simulate a android internal back button?) Something like this:
driver.goPageBack()
Thank you so much. :)
Try this in your Activity code.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.YOUR_ACTIVITY);
assert getSupportActionBar() != null; //null check
getSupportActionBar().setDisplayHomeAsUpEnabled(true); //show back button
}
#Override
public boolean onSupportNavigateUp() {
finish();
return true;
}
In the Flutter repo there is a test that simulates the hardware back button by sending a message over the platform channel like this:
final ByteData message = const JSONMethodCodec().encodeMethodCall(const MethodCall('popRoute'));
await ServicesBinding.instance!.defaultBinaryMessenger.handlePlatformMessage('flutter/navigation', message, (_) { });
https://github.com/flutter/flutter/blob/6688e63f68ebba0919a5fe3c8f8432bd8a65f81b/packages/flutter/test/widgets/router_test.dart#L725-L726
The closest thing I could find is a package that is a back button interceptor that has a method BackButtonInterceptor.popRoute() for testing that emulates the back button press: https://pub.dev/packages/back_button_interceptor

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.

React Native - Module Lifecycle - Dispose resources on "Reload"

I am using a react native module (https://github.com/rusel1989/react-native-bluetooth-serial) for Bluetooth communication with an Arduino.
Eveything works just fine. But when I press "Reload" or the application reloads due to Live Reload being enabled, the onDestroy method of the module is not called. Because of that, the sockets (and streams) are no correctly disposed.
When the reload is finished, I can no longer open a bluetooth socket. It requires me to disable and enable bluetooth, or to restart the application.
Is there ant callback or method I could implement that would correctly dispose these sockets when I reload my application?
Ok after spending time in react-native code I found the answer to this:
On iOS:
You'll have to implement a method called invalidate in your RCTBridgeModule implementation:
That will run whenever the context is destroyed (the app is reloaded) and it will look like this:
- (void)invalidate
{
// disconnect bluetooth here
}
Here's an example of how I did it on iOS.
On Android:
you'll have to implement the onCatalystInstanceDestroy method inside your ReactContextBaseJavaModule and it will look like this:
#Override
public void onCatalystInstanceDestroy() {
// disconnect bluetooth here
}
Here's an example of how I did it on Android.
It seems we can use #Override public void onCatalystInstanceDestroy() {} without the need of implementing anything.
That method will be called before the current JS bundle is destroyed.
on iOS
- (instancetype)init
{
self = [super init];
NSLog(#"whatever you want");
return self;
}
- (void)dealloc
{
// by the way, you do not need the following line because of ARC
// [super dealloc];
NSLog(#"whatever you want");
}
I prefer use dealloc rather than invalidate
because react-native api may change in the future...
on android
import com.facebook.react.bridge.LifecycleEventListener;
import android.util.Log;
public class YourModule extends ReactContextBaseJavaModule implements LifecycleEventListener {
...
YourModule(ReactApplicationContext reactContext) {
super(reactContext);
reactContext.addLifecycleEventListener(this);
this.reactContext = reactContext;
Log.d("YourModuleLog", "whatever you want");
}
#Override
public void onHostResume() {}
#Override
public void onHostPause() {}
#Override
public void onHostDestroy() {
Log.d("YourModuleLog", "not trigger after fast reload");
}
#Override
public void onCatalystInstanceDestroy() {
Log.d("YourModuleLog", "whatever you want");
}
}
only override onCatalystInstanceDestroy does not work for me
unless I also add LifecycleEventListener.

Categories

Resources