ChangeNotifier was used after being disposed - flutter

I am new to Flutter and stuck with this
I have a page that uses a class named GoogleMapsNotifier with ChangeNotifier and when I pop the page I want to dispose the Stream inside this class (last function).
class GoogleMapsNotifier with ChangeNotifier {
final geolocatorService = GeolocatorService();
final placesService = PlacesService();
final markerService = MarkerService();
Position? currentLocation;
List<PlaceSearch> searchResults = [];
StreamController<Place> selectedLocation = BehaviorSubject<Place>();
StreamController<LatLngBounds> bounds = BehaviorSubject<LatLngBounds>();
late Place selectedLocationStatic;
List<Marker> markers = <Marker>[];
GoogleMapsNotifier() {
setCurrentLocation();
}
setCurrentLocation() async {
currentLocation = await geolocatorService.determinePosition();
selectedLocationStatic = Place(
geometry: Geometry(
location: Location(
lat: currentLocation!.latitude, lng: currentLocation!.longitude),
),
name: '',
vicinity: '');
notifyListeners();
}
searchPlaces(String searchTerm) async {
searchResults = await placesService.getAutocomplete(searchTerm);
notifyListeners();
}
setSelectedLocation(String placeId) async {
var sLocation = await placesService.getPlace(placeId);
selectedLocation.add(sLocation);
selectedLocationStatic = sLocation;
searchResults = [];
markers = [];
var newMarker = markerService.createMarkerFromPlace(sLocation);
markers.add(newMarker);
var _bounds = markerService.bounds(Set<Marker>.of(markers));
bounds.add(_bounds as LatLngBounds);
notifyListeners();
}
#override
void dispose() {
selectedLocation.close();
super.dispose();
}
}
and then I have a Go Back button that pops the page and I call this function with Provider before.
onTap: () async {
Provider.of<GoogleMapsNotifier>(context, listen: false)
.dispose();
Navigator.pop(context);
},
It works fine for the first time but when I enter the page for the second time and press Go Back button again, it return an error
Unhandled Exception: A GoogleMapsNotifier was used after being
disposed. E/flutter (13173): Once you have called dispose() on a
GoogleMapsNotifier, it can no longer be used.
How can I fix this?

The Provider should be inside the Route you push. If you use a global provider, the instance of GoogleMapsNotifier will always be the same. Therefore the second time you go on the page it won't work (since it's the same instance you already disposed the first time)
Here is a concrete example
// GOOD
runApp(MaterialApp(...));
...
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => ChangeNotifierProvider<GoogleMapsNotifier>(
create: (_) => GoogleMapsNotifier(),
child: ...,
),
),
);
// BAD
runApp(
ChangeNotifierProvider<GoogleMapsNotifier>(
create: (_) => GoogleMapsNotifier(),
child: MaterialApp(
home: ...,
),
)
);
...
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => ...,
),
);

Related

how to open a url with url_launcher and onTap of InkWell only once?

if i touch it once then it prints out 1 line 123
if i touch it many times then it prints out many line 123
So how when I touch it many times then it prints out 1 line 123 or exiting _launchUrl
When I touch it many times then I also had to go back to that number of times to get rid of _launchUrl
My code here
Hope to get everyone's help!
final Uri _url = Uri.parse('https://flutter.dev');
....
Future<void> _launchUrl() async {
if (!await launchUrl(_url)) {
throw 'Could not launch $_url';
}
}
...
InkWell(
onTap: () {
_launchUrl;
print('123');
}
)
I tried using the wait function but still not getting the desired result
Create a variable buttonPressed and set it default to false
bool buttonPressed = false;
Inside your onTap you can check if the buttonPressed is set to false. If it is set to false you can set it to true and run your _launchUrl function. After you called _launchUrl you can set it back to false to run it again.
if(buttonPressed == false){
buttonPressed = true;
await _launchUrl();
buttonPressed = false;
}
Also mark your onTap as async to use the await keyword
onTap: () async {
I fixed it by following way
Uri uri = Uri.parse('https://flutter.dev');
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => LoadURL(uri),
)
);
// Navigate to here
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';
class LoadURL extends StatelessWidget {
late final Uri uri;
LoadURL(this.uri);
Future<void> _launchUrl(Uri uri) async {
if (!await launchUrl(uri)) {
throw 'Could not launch $uri';
}
}
#override
Widget build(BuildContext context) {
return Center(
child: FutureBuilder(
future: _launchUrl(uri),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
Navigator.pop(context);
}
return CircularProgressIndicator();;
},
),
);
}
}

Go back to login when logged out from the drawer, no matter what

I need to redirect user to login page when he clicks on logout button from drawer (wherever he is). The problem is that when I click on the logout button, the screen remains the same.
According to this post: Flutter provider state management, logout concept
I have:
void main() async {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider<Profile>(
create: (final BuildContext context) {
return Profile();
},
)
],
child: MyApp(),
),
);
}
MyApp:
class _MyAppState extends State<MyApp> {
#override
void initState() {
super.initState();
initPlatformState();
}
/// Platform messages are asynchronous, so we initialize in an async method.
Future<void> initPlatformState() async {
if (!mounted) return;
}
#override
Widget build(BuildContext context) {
return new MaterialApp(
initialRoute: '/',
navigatorKey: navigatorKey,
// ...
home: Consumer<Profile>(
builder: (context, profile, child){
return profile.isAuthenticated ? SplashScreen() : AuthScreen();
}
)
);
}
}
The part of the drawer where there is the logout button:
ListTile(
leading: Icon(Icons.logout),
title: Text(AppLocalizations.of(context)!.logout),
onTap: () async {
SharedPreferences preferences =
await SharedPreferences.getInstance();
await preferences.clear();
final Profile profile =
Provider.of<Profile>(context, listen: false);
profile.isAuthenticated = false;
}),
As I said, when I click on the logout button from the drawer, the user is correctly logged out, but the screen remains the same.
UPDATE
This is the profile class:
class Profile with ChangeNotifier {
bool _isAuthenticated = false;
bool get isAuthenticated {
return this._isAuthenticated;
}
set isAuthenticated(bool newVal) {
this._isAuthenticated = newVal;
this.notifyListeners();
}
}
I think you are using provider class incorrectly.
use your profile class like this.
class Profile with ChangeNotifier {
bool _isAuthenticated = true;
bool get getIsAuthenticated => _isAuthenticated;
set setIsAuthenticated(bool isAuthenticated) {
_isAuthenticated = isAuthenticated;
notifyListeners();//you must call this method to inform lisners
}
}
in set method call notifyListners();
in your listTile
replace profile.isAuthenticated = false to profile.isAuthenticated = false;
Always use getters and setters for best practice.
I hope this is what you were looking for.
Add Navigator.of(context).pushReplacementNamed("/routeName") in LogOut onTap() Section.
For more information : https://api.flutter.dev/flutter/widgets/Navigator/pushReplacementNamed.html
Make sure to have logout route set in MyApp file, and i'd edit logout button file as such:
ListTile(
leading: Icon(Icons.logout),
title: Text(AppLocalizations.of(context)!.logout),
onTap: () async {
SharedPreferences preferences =
await SharedPreferences.getInstance();
await preferences.clear();
final Profile profile =
Provider.of<Profile>(context, listen: false);
profile.isAuthenticated = false;
// add login file route here using Navigator.pushReplacementNamed() ;
}),
Navigator push named -> logout route?

call one asynchronous function when the first one runs flutter

I have two asynchronous functions, one returns a popup, the other makes a permission request. I call them in the init method. But they are called simultaneously, i.e. the first window appears and immediately the second. How do I fix this?
class _MyAppState extends State<MyApp> {
final keyIsFirstLoaded = 'is_first_loaded';
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) async {
final context = MyApp.navKey.currentState.overlay.context;
await showDialogIfFirstLoaded(context);
await initPlatformState();
});
}
showDialogIfFirstLoaded(BuildContext context, prefs) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
bool isFirstLoaded = prefs.getBool(keyIsFirstLoaded);
if (isFirstLoaded == null) {
showDialog(
context: context,
builder: (BuildContext context) {
// return object of type Dialog
return new AlertDialog(
// title: new Text("title"),
content: new Text("//"),
actions: <Widget>[
new FlatButton(
child: new Text(".."),
onPressed: () {
Navigator.of(context).pop();
prefs.setBool(keyIsFirstLoaded, false);
},
),
],
);
},
);
}
}
initPlatformState() async {
print('Initializing...');
await BackgroundLocator.initialize();
print('Initialization done');
final _isRunning = await BackgroundLocator.isRegisterLocationUpdate();
setState(() {
isRunning = _isRunning;
});
onStart();
print('Running ${isRunning.toString()}');
}
add return to showDialog statement, you're not returning a Future so await isn't doing anything
Personal advice: always specify return types, cause if you don't, you get dynamic return type. If you do specify it, the IDE/dart analysis server will help you with problems such as this one.

Unhandled Exception: A Follows was used after being disposed.Once you have called dispose() on a Follows, it can no longer be used

I am new in state Management in flutter with provider package .
How many different cause for generate these types of exception and How can I fix it,
this exception was generate when getFollowing() method was called in didChangeDependencies.
Follows.dart
class Follows with ChangeNotifier{
List<Follow> _following =[];
String userid;
String token;
List<Follow> get followingUser{
return [..._following];
}
void updates(String token,String userid){
this.userid = userid;
this.token = token;
}
Future<void> getFollowing(String id) async {
final response = await http.get("${Domain.ADDRESS}/user/following/$id",headers: {"auth-token" : this.token});
final data =json.decode(response.body)["following"] as List;
List<Follow> followingData =[];
data.forEach((user){
followingData.add(Follow(
id: user["_id"],
username: user["username"],
fullname: user["fullname"],
imageUrl: user["imageUrl"],
followerCount : (user["followers"] as List).length
));
});
_following = [...followingData];
notifyListeners();
}
.........
}
Main.dart
return MultiProvider(
providers: [
ChangeNotifierProvider(
create: (ctx) => Auth(),
),
ChangeNotifierProxyProvider<Auth , Follows>(
create: (ctx)=>Follows(),
update : (context, auth, previous) => Follows()..updates(auth.token, auth.userId)
),
]
child : .......
);
FollowList.dart
class FollowList extends StatefulWidget {
static const followRoutes = "/follow-list";
final String id;
FollowList({this.id});
#override
_FollowListState createState() => _FollowListState();
}
class _FollowListState extends State<FollowList> {
bool isLoading = false;
#override
void didChangeDependencies() {
setState(() {
isLoading = true;
});
Provider.of<Follows>(context,listen: false).getFollowing(widget.id).then((_){
setState(() {
isLoading = false;
});
});
super.didChangeDependencies();
}
#override
Widget build(BuildContext context) {
List<Follow> following = Provider.of<Follows>(context,listen: false).followingUser;
return Scaffold(
appBar: AppBar(title: Text("following),),
body: isLoading ? Center(child: CircularProgressIndicator(strokeWidth: 1,))
: ListView.builder(
itemBuilder: (context, index) => UserCard(
id: following[index].id,
fullname :following[index].fullname,
username :following[index].username,
followerCount : following[index].followerCount,
imageUrl: following[index].imageUrl,
followPressed: true,
),
itemCount: following.length,
),
);
}
}
Please specify where dispose method was called for
Unhandled Exception: A Follows was used after being disposed.
E/flutter ( 8465): Once you have called dispose() on a Follows, it can no longer be used.
ChangeNotifierProxyProvider<Auth , Follows>(
create: (ctx) => Follows(),
//update : (context, auth, previous) => Follows()..updates(auth.token, auth.userId)
// You're creating a new Follow object and disposing the old one
update: (context, auth, previous) => previous..updates(auth.token, auth.userId)
),
Instead of creating a new Follows object try to update the previous one, the listen: false will keep the reference of the old object if the ChangeNotifier updates to the new value
Same problem with me.
I Bring "Future.delayed" to apply this resolved below,
Future.delayed
[/] Your MultiProvider Correct.
#override
void didChangeDependencies() {
setState(() {
isLoading = true;
});
Future.delayed(Duration(milliseconds: 300)).then((_) async {
await Provider.of<Follows>(context, listen: false)
.getFollowing(widget.id)
.then((_) {
setState(() {
isLoading = false;
});
});
});
super.didChangeDependencies();
}
Work for me.

Initial Route if user is logged Flutter

I have created a Future to know if the user is logged, but the initial route isn't save. Then I recive this route in the Initial Route of my material app.
void main() async{
WidgetsFlutterBinding.ensureInitialized();
await UserProvider().isUserLoggedIn();
runApp(MiRoulotte());
}
class MiRoulotte extends StatelessWidget {
final _userProvider = UserProvider();
...
initialRoute: _userProvider.initialRoute,
routes: {
'InitialPage': (BuildContext context) => InitialPage(),
'SignIn': (BuildContext context) => SignInPage(),
'SignUp': (BuildContext context) => SignUpPage(),
'EditProfile': (BuildContext context) => EditProfilePage()
},
)
);
}
}
Future isUserLoggedIn() async{
var user = await _firebaseAuth.currentUser();
if(user != null){
try{
this._currentUser = await getUser(user.uid);
this._initialRoute = 'InitialPage';
}catch(error){
this._initialRoute = 'SignIn';
}
} else{
this._initialRoute = 'SignIn';
}
}
}
photo
You are creating two different instances of UserProvider, that's the problem. You assign the _initialRoute on the first one, but then create a second one which i't assigned.
If you are using Provider, you should use the same instance for that tree and then get it retrieve it through a Consumer for example. Replace your main with:
void main() async{
WidgetsFlutterBinding.ensureInitialized();
UserProvider userProvider = UserProvider();
await userProvider.isUserLoggedIn();
runApp(
Provider<UserProvider>(
create: (_) => userProvider,
builder: (BuildContext context, Widget child) => child,
child: MiRoulotte()),
),
);
}
And then your MiRoulotte fetch it in your build method using a Consumer widget or explicit variable assign:
UserProvider _userProvider = Provider.of<UserProvider>(context, listen: false);
This way you ensure that you are using always the same instance.