Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[mounted value becomes false inside _onBackgroundFetch callback. Due to this i am unable to run setState(() {}). ] #42

Closed
prema6 opened this issue Sep 16, 2019 · 8 comments
Labels

Comments

@prema6
Copy link

prema6 commented Sep 16, 2019

Your Environment

  • Plugin version: background_fetch: ^0.2.0
  • Platform: Android
  • OS version: Android 7.0
  • Device manufacturer / model: Redmi Note 6
  • Flutter info (flutter info, flutter doctor):

Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, v1.7.8+hotfix.4, on Linux, locale en_IN)

[✓] Android toolchain - develop for Android devices (Android SDK version 29.0.2)
[✓] Android Studio (version 3.4)
[!] VS Code (version 1.34.0)
✗ Flutter extension not installed; install from
https://marketplace.visualstudio.com/items?itemName=Dart-Code.flutter
[✓] Connected device (1 available)

  • Plugin config

To Reproduce
Steps to reproduce the behavior:
Calling setState((){}); throws below error when setState() called from BackgroundFetch callback

BackgroundFetch.configure(BackgroundFetchConfig(
        minimumFetchInterval: 15,
        stopOnTerminate: false,
        enableHeadless: true,
        forceReload: false
    ), () async {
      // This is the fetch-event callback.
      print('[BackgroundFetch] Event received');
      //throws error
      setState(() {
        print('@@@@@ background fetch callback - setState worked');
        _events.insert(0, new DateTime.now().toString());
      });     
      BackgroundFetch.finish();
    }).then((int status) {
      print('[BackgroundFetch] SUCCESS: $status');
      setState(() {
        _status = status;
      });
    }).catchError((e) {
      print('[BackgroundFetch] ERROR: $e');
      setState(() {
        _status = e;
      });
    });
    int status = await BackgroundFetch.status;
    setState(() {
      _status = status;
    });
    if (!mounted) return;
  }

Debug logs
D/TSBackgroundFetch( 6511): - configure: {
D/TSBackgroundFetch( 6511): "minimumFetchInterval": 15,
D/TSBackgroundFetch( 6511): "stopOnTerminate": false,
D/TSBackgroundFetch( 6511): "startOnBoot": false,
D/TSBackgroundFetch( 6511): "forceReload": false,
D/TSBackgroundFetch( 6511): "jobService": "com.transistorsoft.flutter.backgroundfetch.HeadlessJobService"
D/TSBackgroundFetch( 6511): }
D/TSBackgroundFetch( 6511): - start
I/flutter ( 6511): [BackgroundFetch] SUCCESS: 2
D/TSBackgroundFetch( 6511): - Background Fetch event received
I/flutter ( 6511): [BackgroundFetch] Event received
E/flutter ( 6511): [ERROR:flutter/lib/ui/ui_dart_state.cc(148)] Unhandled Exception: setState() called after dispose(): _HomePageState#53dc8(lifecycle state: defunct, not mounted)
E/flutter ( 6511): This error happens if you call setState() on a State object for a widget that no longer appears in the widget tree (e.g., whose parent widget no longer includes the widget in its build). This error can occur when code calls setState() from a timer or an animation callback.
E/flutter ( 6511): The preferred solution is to cancel the timer or stop listening to the animation in the dispose() callback. Another solution is to check the "mounted" property of this object before calling setState() to ensure the object is still in the tree.
E/flutter ( 6511): This error might indicate a memory leak if setState() is being called because another object is retaining a reference to this State object after it has been removed from the tree. To avoid memory leaks, consider breaking the reference to this object during dispose().
E/flutter ( 6511): #0 State.setState. (package:flutter/src/widgets/framework.dart:1105:9)
E/flutter ( 6511): #1 State.setState (package:flutter/src/widgets/framework.dart:1140:6)
E/flutter ( 6511): #2 _HomePageState.initPlatformState. (package:demo_mobile/pages/homepage.dart:85:7)
E/flutter ( 6511):
E/flutter ( 6511): #3 BackgroundFetch.configure. (package:background_fetch/background_fetch.dart:169:17)
E/flutter ( 6511): #4 _rootRunUnary (dart:async/zone.dart:1132:38)
E/flutter ( 6511): #5 _CustomZone.runUnary (dart:async/zone.dart:1029:19)
E/flutter ( 6511): #6 _CustomZone.runUnaryGuarded (dart:async/zone.dart:931:7)
E/flutter ( 6511): #7 _BufferingStreamSubscription._sendData (dart:async/stream_impl.dart:336:11)
E/flutter ( 6511): #8 _DelayedData.perform (dart:async/stream_impl.dart:591:14)
E/flutter ( 6511): #9 _StreamImplEvents.handleNext (dart:async/stream_impl.dart:707:11)
E/flutter ( 6511): #10 _PendingEvents.schedule. (dart:async/stream_impl.dart:667:7)
E/flutter ( 6511): #11 _rootRun (dart:async/zone.dart:1120:38)
E/flutter ( 6511): #12 _CustomZone.run (dart:async/zone.dart:1021:19)
E/flutter ( 6511): #13 _CustomZone.runGuarded (dart:async/zone.dart:923:7)
E/flutter ( 6511): #14 _CustomZone.bindCallbackGuarded. (dart:async/zone.dart:963:23)
E/flutter ( 6511): #15 _rootRun (dart:async/zone.dart:1124:13)
E/flutter ( 6511): #16 _CustomZone.run (dart:async/zone.dart:1021:19)
E/flutter ( 6511): #17 _CustomZone.runGuarded (dart:async/zone.dart:923:7)
E/flutter ( 6511): #18 _CustomZone.bindCallbackGuarded. (dart:async/zone.dart:963:23)
E/flutter ( 6511): #19 _microtaskLoop (dart:async/schedule_microtask.dart:41:21)
E/flutter ( 6511): #20 _startMicrotaskLoop (dart:async/schedule_microtask.dart:50:5)
E/flutter ( 6511):

Additional context
Add any other context about the problem here.

@christocracy
Copy link
Member

The /example in this repo calls setState in the fetch-callback.

@christocracy
Copy link
Member

Show me more context of where you've placed BackgroundFetch.configure (ie: the entire component)

@prema6
Copy link
Author

prema6 commented Sep 16, 2019

BackgroundFetch.configure is placed in my homepage.dart
I have the structure as follows:
main.dart -> if(token) => homePage.dart else loginPage.dart

<<<<<<main.dart>>>>>>>>>>>

import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:zoomme_mobile/pages/loginpage.dart';
import 'package:zoomme_mobile/pages/homepage.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:background_fetch/background_fetch.dart';

/// This "Headless Task" is run when app is terminated.
void backgroundFetchHeadlessTask() async {
  print('[BackgroundFetch] Headless event received.');
  print('@@@@@@ PREMA headless Fetch');
  SharedPreferences prefs = await SharedPreferences.getInstance();
  // Read fetch_events from SharedPreferences
  List<String> events = [];
  String json = prefs.getString(EVENTS_KEY);
  if (json != null) {
    events = jsonDecode(json).cast<String>();
  }
  // Add new event.
  events.insert(0, new DateTime.now().toString() + ' [Headless]');
  // Persist fetch events in SharedPreferences
  prefs.setString(EVENTS_KEY, jsonEncode(events));
  BackgroundFetch.finish();
}

void main() async {
  SharedPreferences prefs = await SharedPreferences.getInstance();
  var token = prefs.getString('LastToken');
  //run app
  runApp(new MaterialApp(
    title: 'Demo',
    theme: buildTheme(),
    home: token == null ? LoginPage() : HomePage(),
    routes: <String, WidgetBuilder> {
      //set routes for using the navigator
      '/home': (BuildContext context) => new HomePage(),
      '/login': (BuildContext context) => new LoginPage(),
    },
  ));
  BackgroundFetch.registerHeadlessTask(backgroundFetchHeadlessTask);
}

====================================================================
<<<<< loginPage.dart >>>>>>>>

class LoginPage extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => new _LoginPageState();
}

class _LoginPageState extends State<LoginPage> {

  final emailController = TextEditingController();
  final passwordController = TextEditingController();

  @override
  void initState() {
    super.initState();
    saveCurrentRoute("/login");
  }

  @override
  Widget build(BuildContext context) {
    
    final emailField = TextField(
      obscureText: false,
      style: style,
      controller: emailController,
      decoration: InputDecoration(
          contentPadding: EdgeInsets.fromLTRB(20.0, 15.0, 20.0, 15.0),
          hintText: "Email",
          border: OutlineInputBorder(borderRadius: BorderRadius.circular(32.0))
      ),
    );

    final passwordField = TextField(
      obscureText: true,
      style: style,
      controller: passwordController,
      decoration: InputDecoration(
          contentPadding: EdgeInsets.fromLTRB(20.0, 15.0, 20.0, 15.0),
          hintText: "Password",
          border: OutlineInputBorder(borderRadius: BorderRadius.circular(32.0))),
    );

    final loginButon = Material(
      elevation: 5.0,
      borderRadius: BorderRadius.circular(30.0),
      color: Colors.redAccent,
      child: MaterialButton(
        minWidth: MediaQuery.of(context)
            .size
            .width,
        padding: EdgeInsets.fromLTRB(20.0, 15.0, 20.0, 15.0),
        onPressed: () async {
          await loginAPI(context, emailController.text, passwordController.text).then((ConfigModel loginStatus) {
            if(loginStatus != null) {
              _isLoading = false;
              print('POST login passed');

              Navigator.of(context).pushReplacementNamed('/home');
              return loginStatus.authToken;
            } else {
              setState(() {
                _isLoading = false;
                emailController.text = '';
                passwordController.text = '';
              });
              print('POST login failled');
              return null;
            }
          });
        },
      ),
    );

    return Scaffold(
        body: SingleChildScrollView(
          child: Center(
            child: Container(
              color: Colors.white,
              child: Padding(
                padding: const EdgeInsets.all(36.0),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.center,
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: <Widget>[
                    SizedBox(height: 155.0),
                    SizedBox(height: 45.0),
                    emailField,
                    SizedBox(height: 25.0),
                    passwordField,
                    SizedBox(
                      height: 35.0,
                    ),
                    loginButon,
                    SizedBox(
                      height: 15.0,
                    ),
                  ],
                ),
              ),
            ),
          ),
        ),
    );
  }
}

============================================================================
<<< homePage.dart >>>>

const EVENTS_KEY = "fetch_events";

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() =>  _HomePageState();
}

class _HomePageState extends State<HomePage> {

  Geolocator geoLocator = Geolocator();
  var loginUserName = '';
  int _status = 0;
  List<String> _events = [];
  
  @override
  void initState() {
    super.initState();
    saveCurrentRoute("/home");
    getSharedPreference();
    initPlatformState();
  }

  getSharedPreference() async {
    SharedPreferences preferences = await SharedPreferences.getInstance();
    loginUserName = preferences.getString('LoginUserName');
    
    setState(() {

    });
  }

  // Platform messages are asynchronous, so we initialize in an async method.
  Future<void> initPlatformState() async {
    // Load persisted fetch events from SharedPreferences
    SharedPreferences prefs = await SharedPreferences.getInstance();
    String json = prefs.getString(EVENTS_KEY);
    if (json != null) {
      setState(() {
        _events = jsonDecode(json).cast<String>();
      });
    }

    // Configure BackgroundFetch.
    BackgroundFetch.configure(BackgroundFetchConfig(
        minimumFetchInterval: 15,
        stopOnTerminate: false,
        enableHeadless: true,
        forceReload: false
    ), _onBackgroundFetch).then((int status) {
      print('[BackgroundFetch] SUCCESS: $status');
      setState(() {
        _status = status;
      });
    }).catchError((e) {
      print('[BackgroundFetch] ERROR: $e');
      setState(() {
        _status = e;
      });
    });

    // Optionally query the current BackgroundFetch status.
    int status = await BackgroundFetch.status;
    setState(() {
      _status = status;
    });

    // If the widget was removed from the tree while the asynchronous platform
    // message was in flight, we want to discard the reply rather than calling
    // setState to update our non-existent appearance.
    if (!mounted) return;
  }

  void _onBackgroundFetch() async {
    SharedPreferences prefs = await SharedPreferences.getInstance();

    print('[BackgroundFetch] Event received');

    if(true) {
	//this setState throws error. Mounted value false
	setState(() {
            print('set state PASS');
        });
    }

    BackgroundFetch.finish();
  }

  @override
  Widget build(BuildContext context) => new Scaffold(
    appBar: new AppBar(
      backgroundColor: Colors.red,
      title: new Text('Hi $loginUserName, '),
      actions: <Widget>[
        new FlatButton.icon(
        
        ),
      ],
    ),
    drawer: BasicDrawer(),
    body: new Card(
      child: new Column(
        children: <Widget>[
          ListTile(
            title: Text(''),
          ),
          ListTile(
            title: Text(''),
          )
        ],
      ),
    ),
  );
}

@christocracy
Copy link
Member

Please learn to use Syntax Highlighting. It's very easy with 3-backticks + optional language:

@prema6
Copy link
Author

prema6 commented Sep 16, 2019

okay. Will follow.

@christocracy
Copy link
Member

Please make a simple test-case app that reproduces the problem and share that Github repo with me.

@stale
Copy link

stale bot commented Mar 20, 2021

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. You may also mark this issue as a "discussion" and I will leave this open.

@stale stale bot added the stale label Mar 20, 2021
@stale
Copy link

stale bot commented Jun 2, 2021

Closing this issue after a prolonged period of inactivity. Fell free to reopen this issue, if this still affecting you.

@stale stale bot closed this as completed Jun 2, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants