How to get Android Activity Lifecycle Events in Flutter Plugin? [duplicate] - android

Are there any Activity lifecycle methods in Flutter apps?
Like:
onCreate()
onResume()
onDestroy()
Or:
viewDidload()
viewWillAppear()
How to handle application lifecycle when making an app with Flutter?

There is a method called when the system put the app in the background or return the app to foreground named didChangeAppLifecycleState.
Example with widgets:
class _AppLifecycleReactorState extends State<AppLifecycleReactor> with WidgetsBindingObserver {
#override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
#override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
AppLifecycleState _notification;
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
setState(() { _notification = state; });
}
#override
Widget build(BuildContext context) {
return new Text('Last notification: $_notification');
}
}
Also there are CONSTANTS to know the states that an application can be in, eg:
inactive
paused
resumed
suspending
The usage of these constants would be the value of the constant e.g:
const AppLifecycleState(state)

Run the following code, press the home button and then reopen the app to see it working. There are 4 AppLifecycleState:
resumed: The application is visible and responding to user input.
inactive: The application is in an inactive state and is not receiving user input.
paused: The application is not currently visible to the user, not responding to user input, and running in the background.
detached: The application is still hosted on a flutter engine but is detached from any host views.
Null safe code:
class MyPage extends StatefulWidget {
#override
_MyPageState createState() => _MyPageState();
}
class _MyPageState extends State<MyPage> with WidgetsBindingObserver {
#override
void initState() {
super.initState();
WidgetsBinding.instance!.addObserver(this);
}
#override
void dispose() {
WidgetsBinding.instance!.removeObserver(this);
super.dispose();
}
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
print('Current state = $state');
}
#override
Widget build(BuildContext context) => Scaffold();
}

To be notified when app goes to foreground or route popped you can inherit LifecycleState class and override onResume() and onPause() methods. LifecycleState class:
/// Inherit this State to be notified of lifecycle events, including popping and pushing routes.
///
/// Use `pushNamed()` or `push()` method to track lifecycle events when navigating to another route.
abstract class LifecycleState <T extends StatefulWidget> extends State<T>
with WidgetsBindingObserver {
ResumeResult resumeResult = new ResumeResult();
bool _isPaused = false;
AppLifecycleState lastAppState = AppLifecycleState.resumed;
void onResume() {}
void onPause() {}
/// Use instead of Navigator.push(), it fires onResume() after route popped
Future<T> push<T extends Object>(BuildContext context, Route<T> route, [String source]) {
_isPaused = true;
onPause();
return Navigator.of(context).push(route).then((value) {
_isPaused = false;
resumeResult.data = value;
resumeResult.source = source;
onResume();
return value;
});
}
/// Use instead of Navigator.pushNamed(), it fires onResume() after route popped
Future<T> pushNamed<T extends Object>(BuildContext context, String routeName, {Object arguments}) {
_isPaused = true;
onPause();
return Navigator.of(context).pushNamed<T>(routeName, arguments: arguments).then((value) {
_isPaused = false;
resumeResult.data = value;
resumeResult.source = routeName;
onResume();
return value;
});
}
#override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
#override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.paused) {
if (!_isPaused) {
onPause();
}
} else if (state == AppLifecycleState.resumed &&
lastAppState == AppLifecycleState.paused) {
if (!_isPaused) {
onResume();
}
}
lastAppState = state;
}
}
class ResumeResult {
dynamic data;
String source;
}
Also make sure to start push new routes using push() or pushNamed() method.

Related

Update data every time a screen is shown on device in Flutter

I have a flutter app with redux state management.
In my app, I want to refetch some data from server when some other data in other screen changed. But in my case, I navigate from screen1 to screen2, and some data chaned in screen 2. I use a boolean flag needUpdate saved to redux and set to true after changes on screen 2.
But when I navigate back to screen1, I need to refetch data if the flag is true. But it did not work and data didnt refetch.
Here is some code:
class FirstScreen extends StatefulWidget {
const FirstScreen({Key? key}) : super(key: key);
#override
State<StatefulWidget> createState() => _FirstScreenState();
}
class _FirstScreenState extends State<FirstScreen> {
#override
void initState() {
super.initState();
}
#override
void didChangeDependencies() async {
final store = StoreProvider.of<AppState>(context);
var needUpdate = store.state.commonState?.needUpdate.value ?? false;
if (needUpdate) {
await store.dispatch(prepareData(context));
}
}
#override
Widget build(BuildContext context) {}
}
class SecondScreen extends StatefulWidget {
const SecondScreen({Key? key}) : super(key: key);
#override
State<SecondScreen> createState() => _SecondScreenState();
}
class _SecondScreenState extends State<SecondScreen> {
#override
void initState() {
super.initState();
}
void onPress() async {
await store.dispatch(ChangeNeedUpdatePortfolio(true));
Navigator.pop(context);
}
#override
Widget build(BuildContext context) {}
}
and Here is my action:
ThunkAction<AppState> updatePortfolioData(BuildContext context) {
return (Store<AppState> store) async {
bool needUpdate = store.state.commonState?.needUpdate.value ?? false;
if (needUpdate) {
...
await store.dispatch(ChangeNeedUpdatePortfolio(false));
...
return;
}
};
}

How to automatically hide the status bar after scrolling it down?

I want to automatically hide the status bar after 3 seconds of scrolling it down.
currently, I'm doing this.
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
void initState() {
super.initState();
Timer.periodic(const Duration(seconds: 3), (timer) {
SystemChrome.setEnabledSystemUIOverlays([SystemUiOverlay.bottom]);
});
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: HomeScreen();
);
}
}
I want the timer to begin as soon as the user scrolls the the status bar.
Is there any better way to do that?
You may face existing issue on SystemChrome.
When setting System UI Overlays to bottom or top only, the status bar/bottom will show persistently after clicking.
Flutter problem full-screen on android #23913
SystemChrome.setEnabledSystemUIOverlays isn't sticky on android when only disable top #28426
I provide a workaround solution that detect status bar appear and react to it by using WidgetsBindingObserver
class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
var count = 0;
#override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
...
#override void didChangeMetrics() {
count++;
Future.delayed(const Duration(seconds: 3), () {
if(count == 1) {
SystemChrome.restoreSystemUIOverlays();
}
count --;
});
}
...
#override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}

Flutter setMethodCallHandler not call its argument

iam trying to open a flutter screen from native android
so iam trying to use MethodChannel for returning data to dart then invoke method that's gonna navigate me to the current screen
but my code is not working
this is my code
#Override
public void configureFlutterEngine(#NonNull FlutterEngine flutterEngine) {
super.configureFlutterEngine(flutterEngine);
String channelName = "app_channel";
MethodChannel methodChannel = new MethodChannel(flutterEngine.getDartExecutor(), channelName);
methodChannel.invokeMethod("openCaller", false, new MethodChannel.Result() {
#Override
public void success(#Nullable Object result) {
Log.i("fromInvoke","success" + result.toString());
}
#Override
public void error(String errorCode, #Nullable String errorMessage, #Nullable Object errorDetails) {
Log.i("fromInvoke","failed" + errorMessage);
}
#Override
public void notImplemented() {
Log.i("fromInvoke","not implemented");
}
});
}
}
static const platform =
const MethodChannel('app_channel');
#override
void initState() {
super.initState();
platform.setMethodCallHandler(invokedMethods);
}
and this is a global function
Future<dynamic> invokedMethods(MethodCall methodCall) async {
switch (methodCall.method) {
case "openCaller":
print("arrived to open caller");
// Navigator.pushNamed(context, "/ring");
}
}
The method methodChannel.invokeMethod("openCaller", ...) doesn't seem to be triggered on the code snippets you've provided, it's only set inside configureFlutterEngine. You need to invoke the method from Android to call the function you've configured inside the Flutter app. Check this article covering this topic in more detail and with a sample code.

Flutter: How do I listen to permissions real time

I am working on an app in which I want to continuously listen to location and battery permissions.
Sample scenario:
The user opens the app
Grants permission access
Goes to settings and revokes the permissions
Opens the app again
The app displays a snackbar that informs the user that permission has been revoked.
I am a beginner and I am using the flutter-permissions-handler and the piece of code below shows my usage.
_listenForLocationPermission() {
Future<PermissionStatus> status = PermissionHandler()
.checkPermissionStatus(PermissionGroup.locationWhenInUse);
status.then((PermissionStatus status) {
setState(() {
_permissionStatus = status;
if (_permissionStatus != PermissionStatus.granted) {
_renderOfflineSnackbar('Offline');
}
});
});
}
Any advice on the above is appreciated.
I'm in the same boat and have found that this works
You need to extend your class with WidgetsBindingObserver
class _AppState extends State<App> with WidgetsBindingObserver {
PermissionStatus _status;
...
...
then add these methods to your class
#override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
// check permissions when app is resumed
// this is when permissions are changed in app settings outside of app
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
PermissionHandler()
.checkPermissionStatus(PermissionGroup.locationWhenInUse)
.then(_updateStatus);
}
}
My full code is below, but I've not included the build widget to keep it brief
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';
void main() => runApp(App());
class App extends StatefulWidget {
#override
_AppState createState() => _AppState();
}
class _AppState extends State<App> with WidgetsBindingObserver {
PermissionStatus _status;
// check permissions
#override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
PermissionHandler() // Check location permission has been granted
.checkPermissionStatus(PermissionGroup
.locationWhenInUse) //check permission returns a Future
.then(_updateStatus); // handling in callback to prevent blocking UI
}
#override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
// check permissions when app is resumed
// this is when permissions are changed in app settings outside of app
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
PermissionHandler()
.checkPermissionStatus(PermissionGroup.locationWhenInUse)
.then(_updateStatus);
}
}
override
Widget build(BuildContext context) {
return MaterialApp(
...
...
}
void _updateStatus(PermissionStatus status) {
if (status != _status) {
// check status has changed
setState(() {
_status = status; // update
});
} else {
if (status != PermissionStatus.granted) {
PermissionHandler().requestPermissions(
[PermissionGroup.locationWhenInUse]).then(_onStatusRequested);
}
}
}
}
void _askPermission() {
PermissionHandler().requestPermissions(
[PermissionGroup.locationWhenInUse]).then(_onStatusRequested);
}
void _onStatusRequested(Map<PermissionGroup, PermissionStatus> statuses) {
final status = statuses[PermissionGroup.locationWhenInUse];
if (status != PermissionStatus.granted) {
// On iOS if "deny" is pressed, open App Settings
PermissionHandler().openAppSettings();
} else {
_updateStatus(status);
}
}
I hope this helps
Null safe code:
permission_handler: ^8.0.0+2
The idea is to check for the permission in the app's lifecycle callback when the state is resumed. Here's the minimal code to get you going.
class _FooPageState extends State<FooPage> with WidgetsBindingObserver {
final Permission _permission = Permission.location;
bool _checkingPermission = false;
#override
void initState() {
super.initState();
WidgetsBinding.instance!.addObserver(this);
}
#override
void dispose() {
WidgetsBinding.instance!.removeObserver(this);
super.dispose();
}
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
if (state == AppLifecycleState.resumed && !_checkingPermission) {
_checkingPermission = true;
_checkPermission(_permission).then((_) => _checkingPermission = false);
}
}
Future<void> _checkPermission(Permission permission) async {
final status = await permission.request();
if (status == PermissionStatus.granted) {
print('Permission granted');
} else if (status == PermissionStatus.denied) {
print('Permission denied. Show a dialog and again ask for the permission');
} else if (status == PermissionStatus.permanentlyDenied) {
print('Take the user to the settings page.');
}
}
#override
Widget build(BuildContext context) => Scaffold();
}
If you use flutter_hooks, you can do:
class ExampleWidget extends HookWidget {
...
#override
Widget build(BuildContext context) {
final appLifecycleState = useAppLifecycleState();
useEffect(() {
checkPermissions();
}, [appLifecycleState]);
// Build your widget as normal
return ...
}
}
checkPermissions(); is a function I wrote to actually check permissions, depending on the library. For location, they have it documented in Usage.
Thanks to Flutter hooks, the impact on the existing class is very small.

How to implement a backstack when using the KitKat Transitions Framework

I am using the new KitKat Transitions API on Android. I have created two Scene objects using two layouts. I animate from Scene 1 to Scene 2 inside a Fragment. I want to automatically move back to the previous Scene when the user presses the back button.
Is there some kind of built-in backstack mechanism when using Transitions, or do I have to roll my own?
It is easy enough to call TransitionManager.go(scene1), but I really do not want to implement an onBackPressed() listener in all my fragments that have Scene animations.
I ended up rolling my own solution.
Have your Activity implement this
public interface SceneBackstackHandler {
public void addBackstackListener(BackstackListener listener);
public void removeBackstackListener(BackstackListener listener);
public void removeAllBackstackListeners();
public interface BackstackListener {
public boolean onBackPressed();
}
}
Activity
private final Object mBackstackListenerLock = new Object();
private List<BackstackListener> mBackstackListeners = new ArrayList<>();
#Override
public void onBackPressed() {
synchronized (mBackstackListenerLock) {
for (BackstackListener mBackstackListener : mBackstackListeners) {
if (mBackstackListener.onBackPressed()) {
// handled by fragment
return;
}
}
super.onBackPressed();
}
}
#Override
protected void onPause() {
super.onPause();
removeAllBackstackListeners();
}
#Override
public void addBackstackListener(BackstackListener listener) {
synchronized (mBackstackListenerLock) {
mBackstackListeners.add(listener);
}
}
#Override
public void removeBackstackListener(BackstackListener listener) {
synchronized (mBackstackListenerLock) {
mBackstackListeners.remove(listener);
}
}
#Override
public void removeAllBackstackListeners() {
synchronized (mBackstackListenerLock) {
mBackstackListeners.clear();
}
}
Child Fragment:
public class MySceneFragment extends Fragment
implements SceneBackstackHandler.BackstackListener {
private Scene mCurrentScene;
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
mBackstackHandler = (SceneBackstackHandler) activity;
mBackstackHandler.addBackstackListener(this);
}
#Override
public void onDetach() {
super.onDetach();
mBackstackHandler.removeBackstackListener(this);
}
#Override
public boolean onBackPressed() {
if (mCurrentScene != null && mCurrentScene.equals(mMyScene)) {
removeMyScene();
return true;
}
return false;
}
private void changeScene(Scene scene) {
TransitionManager.go(scene);
mCurrentScene = scene;
}
}
I use an Otto event bus to communicate between my Activity and Fragments. The controlling Activity maintains its own Stack of custom back events which each contain a back action Runnable, i.e. what action should be taken when the back button is pressed.
The advantage to this approach is a slightly more decoupled design and should scale with more fragments. For readability, I have defined the Otto Events inside my Fragment, here, but these could be easily moved elsewhere in your project.
Here's some sample code to give you an idea of how it's done.
Fragment(s)
The Fragment signals its intent to take hold of the next back press by posting a BackStackRequestEvent to the Otto event bus and supplying a Runnable action to be executed when the event is popped off the Activity's custom stack. When the Fragment is detached, it sends a ClearBackStackEvent to the bus to remove any of the Fragment's back actions from the activity's custom stack.
public class MyFragment extends Fragment {
private final String BACK_STACK_ID = "MY_FRAGMENT";
...
public class BackStackRequestEvent {
private Runnable action;
private String id;
public BackStackRequestEvent(Runnable action, String id) {
this.action = action;
this.id = id;
}
public void goBack() {
action.run();
}
public String getId() {
return id;
}
}
public class ClearBackStackEvent {
private String id;
public ClearBackStackEvent(String id) {
this.id = id;
}
public String getId() {
return id;
}
}
...
#Override
public void onDetach() {
super.onDetach();
// Get your Otto singleton and notify Activity that this
// Fragment's back actions are no longer needed
// The Fragment lifecycle stage in which you do this might vary
// based on your needs
EventBus.getInstance().post(new ClearBackStackEvent(BACK_STACK_ID));
}
...
public void someChangeInFragment() {
// Notify the Activity that we want to intercept the next onBackPressed
EventBus.getInstance().post(new BackStackRequestEvent(new Runnable()
{
#Override
public void run() {
// Reverse what we did
doBackAction();
}
}, BACK_STACK_ID)); // constant used later to remove items from Stack
}
}
Activity
The activity registers / unregisters its interest in the events we defined above in onStart() and onStop(). When it receives a new BackStackRequestEvent it adds it to its custom back stack. Once onBackPressed() is called, it pops the back stack and invokes the back action using BackStackRequestEvent.goBack() which in turn runs the Fragment's Runnable. If there is nothing on the Stack, the normal back behaviour is followed.
When the Fragment is detached, the Activity receives a ClearBackStackEvent and it removes all items of the supplied id from the Stack.
public class MyActivity extends Activity {
private Stack<MyFragment.BackStackRequestEvent> customBackStack = new Stack<>();
...
#Override
protected void onStart() {
super.onStart();
EventBus.getInstance().register(this);
}
#Override
protected void onStop() {
super.onStop();
EventBus.getInstance().unregister(this);
}
#Subscribe // Annotation indicating that we want to intercept this Otto event
public void backStackRequested(MyFragment.BackStackRequestEvent request) {
customBackStack.push(request);
}
#Override
public void onBackPressed() {
if (customBackStack.empty()) {
// No custom actions so default behaviour followed
super.onBackPressed();
}
else {
// Pop the custom action and call its goBack() action
MyFragment.BackStackRequestEvent back = customBackStack.pop();
back.goBack();
}
}
#Subscribe
public void clearBackStackRequested(MyFragment.ClearBackStackEvent request) {
String id = request.getId();
for (MyFragment.BackStackRequestEvent backItem : customBackStack) {
if (backItem.getId().contentEquals(id)) {
customBackStack.remove(backItem);
}
}
}
}

Categories

Resources