I need to create a login form. After user successfully login than I need to start some kind of timer (ex: 3 min), so if user has no reaction to app or other word if flutter app state is paused, suspended or inactive more than 3 min. the app will goto main login page. As long as user has interaction with app I need to cancel the timer and only I need to star timer app state is paused, suspended or inactive. How do I do that?
I try to implement the "WidgetsBindingObserver" but its look like is not working as I wanted. If user enters successfully and navigate in app the WidgetsBindingObserver fail (error: state object for widget that no longer appears in the widget tree).
My question is how to implement timed-based flutter app lifecycle, as long as user has interaction with the app? If no user interaction the lifecycle timer will start and if before the timer ends there is a user interaction the timer must be canceled.
class _MyUserHomePageState extends State<MyUserHomePage> with WidgetsBindingObserver {
AppLifecycleState _appLifecycleState;
#override
void initState() {
_appStatePasue = false;
WidgetsBinding.instance.addObserver(this);
super.initState();
}
// TODO: DID_CHANGE_APP_LIFE_CYCLE
void didChangeAppLifecycleState(AppLifecycleState state) {
setState(() {
_appLifecycleState = state;
if(_appLifecycleState == AppLifecycleState.paused ||
_appLifecycleState == AppLifecycleState.inactive ||
_appLifecycleState == AppLifecycleState.suspending) {
_appStatePasue = true;
print("timer---fired: $_appLifecycleState");
_timer = Timer.periodic(Duration(minutes: 1), _capitalCallback);
print(_appLifecycleState);
} else {
_appStatePasue = false;
}
});
}
// TODO: APP_LIFE_CYCLE__CALLBACK
void _capitalCallback(_timer) {
if(_appStatePasue == true) {
_timer.cancel();
print("return---main---page: $_appLifecycleState");
setState(() {
Navigator.push(
context,
SlideRightRoute(widget: MyApp())
);
});
} else {
_timer.cancel();
print("timer---canceled: $_appLifecycleState");
}
}
#override
void dispose() {
super.dispose();
}
#override
void onDeactivate() {
super.deactivate();
}
#override
Widget build(BuildContext context) {
return new Scaffold (
);
}
}
You can use the Timer class to trigger a log out function after 3 minutes of inactivity. Something you can try is to wrap your entire app in a GestureDetector that resets the timer on any event. You'd just have to make sure that any other GestureDetectors in your app use HitTestBehavior.translucent so the events are propagated to your root listener. Here's a full example:
import 'dart:async';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) => AppRoot();
}
class AppRoot extends StatefulWidget {
#override
AppRootState createState() => AppRootState();
}
class AppRootState extends State<AppRoot> {
Timer _timer;
#override
void initState() {
super.initState();
_initializeTimer();
}
void _initializeTimer() {
_timer = Timer.periodic(const Duration(minutes: 3), (_) => _logOutUser);
}
void _logOutUser() {
// Log out the user if they're logged in, then cancel the timer.
// You'll have to make sure to cancel the timer if the user manually logs out
// and to call _initializeTimer once the user logs in
_timer.cancel();
}
// You'll probably want to wrap this function in a debounce
void _handleUserInteraction([_]) {
if (!_timer.isActive) {
// This means the user has been logged out
return;
}
_timer.cancel();
_initializeTimer();
}
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: _handleUserInteraction,
onPanDown: _handleUserInteraction,
onScaleStart: _handleUserInteraction,
// ... repeat this for all gesture events
child: MaterialApp(
// ... from here it's just your normal app,
// Remember that any GestureDetector within your app must have
// HitTestBehavior.translucent
),
);
}
}
UPDATE: I just discovered the Listener class which might make more sense here than the GestureDetector. I've personally never used it, but feel free to experiment! Check out the documentation on gestures for more info.
Update to Kirollos Morkos's Answer
We have used NavigatorState key to logout.
Here is the full code of AppRootState.
class AppRootState extends State<AppRoot> {
Timer _timer;
bool forceLogout = false;
final navigatorKey = GlobalKey<NavigatorState>();
#override
void initState() {
super.initState();
_initializeTimer();
}
void _initializeTimer() {
_timer = Timer.periodic(const Duration(minutes: 10), (_) => _logOutUser());
}
void _logOutUser() {
// Log out the user if they're logged in, then cancel the timer.
// You'll have to make sure to cancel the timer if the user manually logs out
// and to call _initializeTimer once the user logs in
_timer.cancel();
setState(() {
forceLogout = true;
});
}
// You'll probably want to wrap this function in a debounce
void _handleUserInteraction([_]) {
print("_handleUserInteraction");
_timer.cancel();
_initializeTimer();
}
void navToHomePage(BuildContext context) {
//Clear all pref's
SharedPreferencesHelper.clearAllValues();
navigatorKey.currentState.pushAndRemoveUntil(
MaterialPageRoute(builder: (context) => LoginPage()),
(Route<dynamic> route) => false);
}
#override
Widget build(BuildContext context) {
if (forceLogout) {
print("ForceLogout is $forceLogout");
navToHomePage(context);
}
return GestureDetector(
onTap: _handleUserInteraction,
onPanDown: _handleUserInteraction,
onScaleStart: _handleUserInteraction,
// ... repeat this for all gesture events
child: MaterialApp(
navigatorKey: navigatorKey,
// ...
// ...
));
}
}
For anyone having issues with Navigating, Simple create class with a context as static parameter and then set the context from any of your first widgets in the app, then you can use the context in your timeout function
create class:
class ContextClass{ static BuildContext CONTEXT; }
set the context from any of your first widget build method like so
ContextClass.CONTEXT=context;
And use in your time out function like so
Navigator.of(ContextClass.CONTEXT).pushNamedAndRemoveUntil('<Your Route>', (Route<dynamic> route) => false);
To access Timer for every screen as well as close all the screens after session timeout and open Login Screen.
Define Session Expiry time at separately Constants.dart file as static.
static const int sessionExpireTimeout = 30; //in seconds
Now after Successful login, at next screen i.e. HomeScreen(), initialize a method called Future.delayed() with expiry time inside Widget build(BuildContext context) method:
Future.delayed(const Duration(seconds: Constants.sessionTimeout), () async {
await FirebaseAuth.instance.signOut(); // Firebase Sign out before exit
// Pop all the screens and Pushes Login Screen only
Navigator.of(context)
.pushNamedAndRemoveUntil(LoginScreen(), (route) => false);
});
Remember that you don't have to Pop this HomeScreen() while using Navigator.
Whenever you want to navigate to another screen. Use pushNamed() or push() method.
Then after switching to another screen, you can use any Navigator method.
Related
I've tested Geofence example by cn1 where it sets local notification. When the app is closed(get destroyed), it still gives notification. But I want to get location through GPS and run connectionRequest to save them in the server. I replaced the connectionRequest code instead of LocalNotification in following code but it doesnot work. What should I do to run the connectionRequest when the app is closed(not when it is minimized but destroyed) so that once the user installs and close (destroys) it, the app sent his/her location data in the server forever untill the app is uninstalled.
Geofence gf = new Geofence("test", loc, 100, 100000);
LocationManager.getLocationManager().addGeoFencing(GeofenceListenerImpl.class, gf);
Geofence with localNotification:
public class GeofenceListenerImpl implements GeofenceListener {
#Override
public void onExit(String id) {
}
#Override
public void onEntered(String id) {
if(Display.getInstance().isMinimized()) {
Display.getInstance().callSerially(() -> {
Dialog.show("Welcome", "Thanks for arriving", "OK", null);
});
} else {
LocalNotification ln = new LocalNotification();
ln.setId("LnMessage");
ln.setAlertTitle("Welcome");
ln.setAlertBody("Thanks for arriving!");
Display.getInstance().scheduleLocalNotification(ln, 10, LocalNotification.REPEAT_NONE);
}
}
}
Why the following doesnot work? (it only work when the app is running or is minimized but not when it is destroyed.)
public class GeofenceListenerImpl implements GeofenceListener {
#Override
public void onExit(String id) {
System.out.println("geofence onExit");
}
#Override
public void onEntered(String id) {
if(Display.getInstance().isMinimized()) {
Display.getInstance().callSerially(() -> {
System.out.println("geofence isMinimized");
});
} else {
System.out.println("geofence when app is closed");
//I want to run connectionRequest here but is not working
}
}
}
PS. I've used background fetch but it only works when the app is minimized.
Update1: Demo of how I used connectionRequest outside of minimized() method...
public class GeofenceListenerImpl implements GeofenceListener {
#Override
public void onExit(String id) {
System.out.println("geofence onExit");
}
#Override
public void onEntered(String id) {
if(Display.getInstance().isMinimized()) {
Display.getInstance().callSerially(() -> {
});
} else {
System.out.println("geofence when app is closed");
Connection c = new Connection();
c.liveTrackConnectionMethod("22" , "23");
}
}
}
Connection class
public class Connection {
ArrayList<Map<String, Object>> response;
public void liveTrackConnectionMethod(String lat, String lon) {
ConnectionRequest cr = new ConnectionRequest() {
#Override
protected void readResponse(InputStream input) throws IOException {
JSONParser jSONParser = new JSONParser();
Map parser = jSONParser.parseJSON(new InputStreamReader(input));
response = null;
}
};
cr.setPost(true);
cr.setUrl("http://url.com");
cr.addArgument("userid", Preferences.get(AllUrls.userIdPreference, null));
cr.addArgument("lat", lat + "");
cr.addArgument("long", lon + "");
cr.addRequestHeader("Accept", "application/json");
NetworkManager.getInstance().addToQueueAndWait(cr);
}
}
I think an app will always return false for isMinimized() when the app is closed or minimized (i.e. not currently running in the foreground) I may be wrong about this.
Try calling your connectionRequest script outside the isMinimized(). After all, you will want to keep track of user location whether they are using the app or not.
Your first solution with LocalNotification will show users a notification by calling the else part, rather than the Dialog when they're using the app, because isMinimized() will be false.
I'm performing a Database operation in the doInBackground method inside an AsyncTask on Android.
For some reason, the UI get blocked during the 5-6 seconds that the operation takes.
It does not make any sense for me, the operacion inside the doInBackground should not be executed in the UI Thread, right?
Here is my code:
private class CountItems extends AsyncTask<String, Void, Integer> {
private ProgressDialog dialog;
#Override
protected void onPreExecute() {
dialog = new ProgressDialog(context);
dialog.setCancelable(false);
dialog.setMessage(getString(R.string.asynTask_loading));
dialog.show();
}
#Override
protected Integer doInBackground(String... params) {
// This operation takes 5-6 seconds.
return app.databaseSession().getMyObjectDao().count(selectedProject, filter, null, false);
}
#Override
protected void onPostExecute(Integer result) {
counterTextView.setText(String.valueOf(result));
if (dialog.isShowing()) {
dialog.dismiss();
}
}
}
I've made a test. If I put a Thread.sleep() inside the doInBackground method, it is executed in a different Thread without blocking the UI.
Like this:
#Override
protected Integer doInBackground(String... params) {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return 0;
}
Any ideas?
Thanks in advance.
I am using the MapContainer(cn1lib). so in android devices low relsolution the zoom works fine. But in android devices high resolution the zoom not works fine. The zoom in stay far. i attach a screen with the to max zoom in, it is a bug or i'm wrong?
SCREENSHOT
GUI-DESIGN
public class StateMachine extends StateMachineBase {
MapContainer mapContainer;
public StateMachine(String resFile) {
super(resFile);
// do not modify, write code in initVars and initialize class members there,
// the constructor might be invoked too late due to race conditions that might occur
}
/**
* this method should be used to initialize variables instead of the
* constructor/class scope to avoid race conditions
*/
protected void initVars(Resources res) {
}
#Override
protected void beforeShow(Form f) {
try {
this.mapContainer.setShowMyLocation(true);
this.mapContainer.zoom(new Coord(20.640086, -103.432207), 17);
this.mapContainer.setCameraPosition(new Coord(20.640086, -103.432207));
this.mapContainer.addMarker(
EncodedImage.createFromImage(fetchResourceFile().getImage("pin.png"), false),
new Coord(20.640086, -103.432207),
"Hi marker", "Optional long description",
new ActionListener() {
public void actionPerformed(ActionEvent evt) {
Dialog.show("Marker Clicked!", "You clicked the marker", "OK", null);
}
}
);
this.mapContainer.addPointerDraggedListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent evt) {
mapContainer.clearMapLayers();
mapContainer.addMarker(EncodedImage.createFromImage(fetchResourceFile().getImage("pin.png"), false), mapContainer.getCameraPosition(), "Hi marker", "Optional long description", new ActionListener() {
public void actionPerformed(ActionEvent evt) {
Dialog.show("Marker Clicked!", "You clicked the marker", "OK", null);
}
});
}
});
} catch (Exception ex) {
ex.printStackTrace();
}
super.beforeShow(f); //To change body of generated methods, choose Tools | Templates.
}
#Override
protected Component createComponentInstance(String componentType, Class cls) {
if (cls == MapComponent.class) {
this.mapContainer = new MapContainer();
return this.mapContainer;
}
return super.createComponentInstance(componentType, cls); //To change body of generated methods, choose Tools | Templates.
}
}
That is a MapComponent not a native map, so it uses the old open street maps support and relatively simple map rendering even on the device. We have support for native google maps which isn't exposed in the GUI builder but you can add it thru code.
This will embed the actual native GUI into place which will both look and feel better on the device although it will look the same on the simulator.
I would like to catch an observable thats loading an add and have it show after a few seconds. Im calling this is multiple places but in one particular place i'd like it to only run after a few seconds have elapsed. Here is the method i have that returns an observable:
private Observable fullScreenAdObservable(){
// Create the interstitial.
return Observable.create(new Observable.OnSubscribe<Object>() {
#Override
public void call(Subscriber<? super Object> subscriber) {
interstitial = new InterstitialAd(main.this);
interstitial.setAdUnitId(admob_publisherID);
// Create ad request.
AdRequest adRequest = new AdRequest.Builder().build();
// Begin loading your interstitial.
interstitial.loadAd(adRequest);
interstitial.setAdListener(new AdListener() {
#Override
public void onAdLoaded() {
super.onAdLoaded();
interstitial.show();
}
});
}
});
}
then to subscribe i do this but the timer one fails:
fullScreenAdObservable().subscribe();//this works
fullScreenAdObservable().timer(0,3, TimeUnit.SECONDS).subscribe(); //this fails to react,why ?
I want the timer to run the observerable after 3 seconds but it wont, why ?
There seems to be some scheduling issue when using delay and timer. So i had to explicity tell what thread i want to subscribe on BEFORE calling the delaySubscription or timer. Also modified the subscriber to listen for onNext so i can pass the subscriber a ad. Then i actually showed the ad in the onNext (as its triggered after the delay is elapsed.
private Observable fullScreenAdObservable(){
// Create the interstitial.
return Observable.create(new Observable.OnSubscribe<Object>() {
#Override
public void call(final Subscriber<? super Object> subscriber) {
interstitial = new InterstitialAd(main.this);
interstitial.setAdUnitId(admob_publisherID);
// Create ad request.
AdRequest adRequest = new AdRequest.Builder().build();
// Begin loading your interstitial.
interstitial.loadAd(adRequest);
interstitial.setAdListener(new AdListener() {
#Override
public void onAdLoaded() {
super.onAdLoaded();
//emit a loaded ad
subscriber.onNext(interstitial);
}
});
}
});
}
//and to call it :
fullScreenAdObservable().subscribeOn(AndroidSchedulers.mainThread()).
delaySubscription(13, TimeUnit.SECONDS).subscribe(new Action1<InterstitialAd>() {
#Override
public void call(InterstitialAd ad) {
ad.show();
}
});
I would like to reset/ pause / resume the clock timer which is in the AlarmClock using an intent (mainly would be from another device).
the following code outlines my activity to launch the timer with a predefined value. My question is how to reset/ pause / resume that timer from the same activity.
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// launch the timer with a predefined value
Intent startTimer=new Intent(AlarmClock.ACTION_SET_TIMER);
startTimer.putExtra(AlarmClock.EXTRA_LENGTH, 300);
startTimer.putExtra(AlarmClock.EXTRA_SKIP_UI, false);
startTimer.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(startTimer);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
}