Creating StreamProvider in flutter app needs correction - flutter

I am learning about StreamProviders and ChangeNotifierProvider and how to use them in a flutter app.
The problem I am having is when I create the StreamProvider in main.dart. I am getting this error
Instance member 'getAgencyTrxn' can't be accessed using static access. (Documentation)
as designated by a red line under getAgencyTrxn(). I have been following a tutorial and also some posts here but none of them quite match what I am doing.
How do I fix this error?
Here is what I have so far:
main.dart
Widget build(BuildContext context) {
Provider.debugCheckInvalidValueType = null;
globals.newTrxn = true;
return MultiProvider(
providers: [
ChangeNotifierProvider<TrxnProvider>(create: (context) => TrxnProvider()),
StreamProvider<TrxnProvider>(
create: (context) => TrxnProvider.getAgencyTrxn(),
initialData: []),
],
child: MaterialApp(
debugShowCheckedModeBanner: false,
home: LoginScreen(),
),
);
}
trxn_provider.dart
class TrxnProvider extends ChangeNotifier {
final firestoreService = FirestoreService();
String? _clientFName;
String? _clientLName;
// Getters
String? get clientFName => _clientFName;
String? get clientLName => _clientLName;
// Setters
changeclientFName(String value) {
_clientFName = value;
notifyListeners();
}
changeclientLName(String value) {
_clientLName = value;
notifyListeners();
}
loadValues(QueryDocumentSnapshot trxns) {
_clientFName = trxns['clientFName'];
_clientLName = trxns['clientLName'];
}
getAgencyTrxn() {
return firestoreService.getAgencyTrxns();
}
saveTrxn() {
if (globals.newTrxn == true) {
_trxnId = uuId.v4();
globals.newTrxn = false;
}
var newTrxn = Trxns(
clientFName: clientFName,
clientLName: clientLName);
firestoreService.saveTrxn(newTrxn);
}
deleteTrxn(String trxnId) {
firestoreService.deleteTrxn(trxnId);
}
}
firestore_service.dart
class FirestoreService {
FirebaseFirestore _db = FirebaseFirestore.instance;
Stream<QuerySnapshot> getAgencyTrxns() async* {
yield* FirebaseFirestore.instance
.collection('agency').doc(globals.agencyId)
.collection('trxns')
.where('trxnStatus', isNotEqualTo: 'Closed')
.snapshots();
}
}

I found the solution. I needed to change this
create: (context) => TrxnProvider.getAgencyTrxn()
to this
create: (context) => TrxnProvider().getAgencyTrxn()

Related

Change notifier provider is not updating the consumer in main

I am trying to set the theme of my app on the response of login data after getting the role but my theme is not updating as per expectation. this is how my main() looks. my code is showing no error and I tried to debug nothing seems wrong.
Widget build(BuildContext context) {
return ChangeNotifierProvider<ThemeModel>(
create: (_) => ThemeModel(),
child: Consumer<ThemeModel>(
builder: (context, ThemeModel themeNotifier, child) {
return Sizer(builder: (context, orientation, deviceType) {
return MaterialApp(
theme: themeNotifier.theme == 'consultant'
? counsultantApptheme()
: themeNotifier.theme == 'rmo'
? rmoApptheme()
: counsultantApptheme(),
navigatorKey: navigatorKey,
debugShowCheckedModeBanner: false,
initialRoute: startroute.toString(),
routes: routes,
);
});
}));
and this how I am updating after response of login API
if (snapshot.data!.data!.consultantYN == 'Y') {
Provider.of<ThemeModel>(context, listen: false).theme =
'consultant';
} else {
Provider.of<ThemeModel>(context, listen: false).theme = 'rmo';
}
and this is my function where I am setting theme and calling notifyListeners() in class extends by ChangeNotifier
//theme_model.dart
import 'package:flutter/material.dart';
import 'package:nmc/widgets/theme_config/theme_preference.dart';
class ThemeModel extends ChangeNotifier {
late String _theme;
late ThemePreferences _preferences;
String get theme => _theme;
ThemeModel() {
_theme = 'default';
_preferences = ThemePreferences();
getPreferences();
}
//Switching themes in the flutter apps - Flutterant
set theme(String value) {
_theme = value;
_preferences.setTheme(value);
notifyListeners();
}
getPreferences() async {
_theme = await _preferences.getTheme();
notifyListeners();
}
}

Flutter and AWS Amplify user Auth

I'm trying to implement an authentication flow (user login, log out, autologin etc) in Flutter using AWS Amplify and there is something I can't fixed. I like to do this as clean as possible, so I'm not using third-party packages apart from the AWS ones and Provider for state management.
Autologin is the thing that is not working. I need to hot refresh the app so autologin works. (normally this points towards an state management issue)
I'm not having errors or exceptions of any kind apart from this suspicious output:
D/AWSMobileClient(30193): _federatedSignIn: Putting provider and token in store
D/AWSMobileClient(30193): Inspecting user state details
D/AWSMobileClient(30193): hasFederatedToken: false provider: cognito-idp.ap-southeast-2.amazonaws.com/ap-southeast-2_Fd5bKVAbV
Any help is welcome.
main:
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => Tracks()),
ChangeNotifierProvider(create: (_) => Player()),
ChangeNotifierProvider(create: (_) => User()),
Provider(create: (_) => SearchTracksService()),
Provider(create: (_) => AuthenticationServices()),
],
child: Builder(
builder: (context) {
Commands.init(context);
AuthenticationCommands().getCurrentUser();
//AuthenticationCommands().fetchSession();
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Music Player',
theme: theme,
home: Provider.of<User>(context, listen: false).isAuthenticated
? HomeScreen()
: AuthenticationScreen(),
routes: {
HomeScreen.routeName: (ctx) => HomeScreen(),
AuthenticationScreen.routeName: (ctx) => AuthenticationScreen(),
SignUpConfirmationScreen.routeName: (ctx) =>
SignUpConfirmationScreen(),
},
);
},
),
);
}
}
Future<void> getCurrentUser() async {
try {
var currentUser = await authenticationServices.getCurrentUser();
if (currentUser != null) {
user.id = currentUser.userId;
user.name = currentUser.username;
user.email = currentUser.username;
user.isAuthenticated = true;
return;
}
user.isAuthenticated = false;
} catch (e) {
user.isAuthenticated = false;
throw e;
}
}
Future<AuthUser?> getCurrentUser() async {
try {
return await Amplify.Auth.getCurrentUser();
} catch (e) {
print(e);
throw (e);
}
}
import 'package:flutter/foundation.dart';
class User extends ChangeNotifier {
bool _isAuthenticated = false;
late String _id;
late String _email;
late String _name;
bool get isAuthenticated {
return _isAuthenticated;
}
set isAuthenticated(bool isAuthenticated) {
_isAuthenticated = isAuthenticated;
notifyListeners();
}
String get name {
return _name;
}
set name(String name) {
_name = name;
notifyListeners();
}
String get id {
return _id;
}
set id(String id) {
_id = id;
notifyListeners();
}
String get email {
return _email;
}
set email(String email) {
_email = email;
notifyListeners();
}
}
I also got the "false provider" error message in the console of my IDE while using the Android simulator to run an Amplify/Flutter app.
For me, the user does not get signed in at all when I see those errors in the console - and I think that those two issues are related.
So I opened an issue with the repository regarding this here, you may want to watch it because it may answer this SO issue you've raised: https://github.com/aws-amplify/amplify-flutter/issues/1204

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.

Provider rebuilds the widget, but nothing shows up until a "Hot restart"

I am building a flutter app and I get some data from a future, I also got the same data with a changenotifier. Well the logic is that while some object doesn't have data because its waiting on the future then display a spinning circle. I have already done this in the app and I have a widget called Loading() when the object has not received data. The problem I have run into is that I get the data, but it doesn't display anything.
the data displays correctly until I perform a hot refresh of the app. a capital R instead of a lowercase r. The difference is that it starts the app and deletes all aggregated data.
when this happens it seems that the data fills the object but I hypothesize that it is becoming not null meaning [] which is empty but not null and is displaying the data "too quickly" this in turn displays nothing for this widget until I restart "r" which shows me the above screenshot.
here is the offending code.
import 'package:disc_t/Screens/LoggedIn/Classes/classTile.dart';
import 'package:disc_t/Screens/LoggedIn/Classes/classpage.dart';
import 'package:disc_t/Screens/LoggedIn/Classes/classpageroute.dart';
import 'package:disc_t/Services/database.dart';
import 'package:disc_t/models/user.dart';
import 'package:disc_t/shared/loading.dart';
import 'package:flutter/material.dart';
import 'package:morpheus/page_routes/morpheus_page_route.dart';
import 'package:provider/provider.dart';
class ClassList extends StatefulWidget {
#override
_ClassListState createState() => _ClassListState();
}
class _ClassListState extends State<ClassList> {
#override
void initState() {
ClassDataNotifier classdatanotif =
Provider.of<ClassDataNotifier>(context, listen: false);
// final user = Provider.of<User>(context);
// getTheClasses(classdatanotif);
// List<ClassData> d = classes;
}
#override
Widget build(BuildContext context) {
ClassDataNotifier classdatanotif = Provider.of<ClassDataNotifier>(context);
List<ClassData> cData = Provider.of<List<ClassData>>(context);
bool rebd = false;
Widget checker(bool r) {
if (cData == null) {
return Loading();
} else {
if (rebd == false) {
setState(() {
rebd = true;
});
rebd = true;
return checker(rebd);
// return Text("Still Loading");
} else {
return PageView.builder(
scrollDirection: Axis.horizontal,
itemCount: cData.length,
// controller: PageController(viewportFraction: 0.8),
itemBuilder: (context, index) {
return Hero(
tag: cData[index],
child: GestureDetector(
onTap: () {
// Navigator.of(context).push(ClassPageRoute(cData[index]));
Navigator.push(
context,
MorpheusPageRoute(
builder: (context) =>
ClassPage(data: cData[index]),
transitionToChild: true));
},
child: ClassTile(
classname: cData[index].classname,
description: cData[index].classdescription,
classcode: cData[index].documentID,
),
),
);
});
}
}
}
return checker(rebd);
}
}
here is how the provider is implemented
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
// final DatabaseService ds = DatabaseService();
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
StreamProvider<User>.value(
value: AuthService().user,
// child: MaterialApp(
// home: Wrapper(),
// ),
),
ChangeNotifierProvider<ClassDataNotifier>(
create: (context) => ClassDataNotifier(),
),
FutureProvider(
create: (context) => DatabaseService().fetchClassdata,
)
],
child: MaterialApp(home: Wrapper()),
);
}
}
and here is the function that is ran to get the data
Future<List<ClassData>> get fetchClassdata async {
QuerySnapshot snapshot = await classesCollection.getDocuments();
List<ClassData> _classList = List<ClassData>();
snapshot.documents.forEach((element) async {
QuerySnapshot pre = await Firestore.instance
.collection("Classes")
.document(element.documentID)
.collection("Pre")
.getDocuments();
List<Preq> _preList = List<Preq>();
pre.documents.forEach((preClass) {
Preq preqData = Preq.fromMap(preClass.data);
if (preClass.data != null) {
_preList.add(preqData);
}
});
ClassData data =
ClassData.fromMap(element.data, element.documentID, _preList);
if (data != null) {
_classList.add(data);
}
});
return _classList;
}
I think the logic of your provider is fine, the problem lies in the line
snapshot.documents.forEach((element) async {
...
}
The forEach is not a Future (what is inside it's a future because the async, but the method itself not) so the code runs the first time, it reaches the forEach which does its own future on each value and propagate to the next line of code, the return, but the list is empty because the forEach isn't done yet.
There is a special Future.forEach for this case so you can wait for the value method before running the next line
Future<List<ClassData>> get fetchClassdata async {
QuerySnapshot snapshot = await classesCollection.getDocuments();
List<ClassData> _classList = List<ClassData>();
await Future.forEach(snapshot.documents, (element) async {
QuerySnapshot pre = await Firestore.instance
.collection("Classes")
.document(element.documentID)
.collection("Pre")
.getDocuments();
List<Preq> _preList = List<Preq>();
pre.documents.forEach((preClass) {
Preq preqData = Preq.fromMap(preClass.data);
if (preClass.data != null) {
_preList.add(preqData);
}
});
ClassData data =
ClassData.fromMap(element.data, element.documentID, _preList);
if (data != null) {
_classList.add(data);
}
});
return _classList;
}
Here is a similar problem with provider with a forEach. Maybe it can help you understand a bit better

Retrieving Runtime Changed ThemeData Problem

My story in short is, I can successfully change app theme dynamically, but I fail when it comes to start my app with the last chosen ThemeData.
Here is the main.dart:
import "./helpers/constants/themeConstant.dart" as themeProfile;
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return MultiProvider(
providers: [
//Several ChangeNotifierProviders
],
child: Consumer<AuthenticateProvider>(
builder: (ctx, authData, _) => ChangeNotifierProvider<ThemeChanger>(
create: (_) {
ThemeData themeToBeSet;
themeProfile.setInitialTheme().then((themeData) {
themeToBeSet = themeData;
});
return ThemeChanger(themeToBeSet);
},
child: _MaterialAppWithTheme(authData),
)
)
);}}
The problem is themeToBeSet variable always being null eventhough I set a ThemeData as I do below:
ThemeData selectedTheme;
Future<ThemeData> setInitialTheme() async {
final preferences = await SharedPreferences.getInstance();
if (!preferences.containsKey(ApplicationConstant.sharedTheme)) {
selectedTheme = appThemeDataDark;
final currentThemeInfo = json.encode({
"themeStyle": ApplicationConstant.darkAppTheme
});
preferences.setString(ApplicationConstant.sharedTheme, currentThemeInfo);
return selectedTheme;
}
else {
final extractedThemeInfo = json.decode(preferences.getString(ApplicationConstant.sharedTheme)) as
Map<String, dynamic>;
final chosenTheme = extractedThemeInfo["themeStyle"];
if (chosenTheme == ApplicationConstant.lightAppTheme) {
selectedTheme = appThemeDataLight;
return selectedTheme;
}
else if (chosenTheme == ApplicationConstant.darkAppTheme) {
selectedTheme = appThemeDataDark;
return selectedTheme;
}
else {
selectedTheme = appThemeDataDark;
return selectedTheme;
}}}
Here, I used shared_preferences.dart package to store and retrieve ThemeData info. If I debug this block, I see that my selectedTheme variable is set one of these ThemeData successfully. But, for a reason I couldn't able to find out, themeToBeSet variable on main.dart is not assigned to the result of my setInitialTheme() method.
Is it because of being asynchronous? But, isn't Dart waiting an asynchronous method with .then()?
In order not to leave any questionmarks realated for my other sections, I'm also sharing ThemeChanger class,
class ThemeChanger with ChangeNotifier {
ThemeData _themeData;
ThemeChanger(
this._themeData
);
getTheme() => _themeData;
setTheme(ThemeData theme) {
_themeData = theme;
notifyListeners();
}
}
And, _MaterialAppWithTheme,
class _MaterialAppWithTheme extends StatelessWidget {
final AuthenticateProvider authData;
_MaterialAppWithTheme(
this.authData,
);
Widget build(BuildContext context) {
final theme = Provider.of<ThemeChanger>(context);
return MaterialApp(
title: 'Game Shop Demo',
theme: theme.getTheme(),
home: authData.isLogedin ?
HomeScreen(authData.userId) :
FutureBuilder(
future: authData.autoLogin(),
builder: (ctx, authResult) => authResult.connectionState == ConnectionState.waiting ?
SplashScreen():
LoginScreen()
),
routes: {
//Several named routes
},
);
}
}
As I suspected, I misused .then().
I thought Dart is awaiting when you use .then() but after running into this post, I learnt that it is not awaiting..
So, I carry setInitialTheme() method to ThemeChanger class (it was in a different class previously) and call it in the constructor. Here its final version,
class ThemeChanger with ChangeNotifier {
ThemeData _themeData;
ThemeChanger() {
_setInitialTheme();
}
getTheme() => _themeData;
setTheme(ThemeData theme) {
_themeData = theme;
notifyListeners();
}
Future<ThemeData> _setInitialTheme() async {
final preferences = await SharedPreferences.getInstance();
if (!preferences.containsKey(ApplicationConstant.sharedTheme)) {
_themeData = appThemeDataDark;
final currentThemeInfo = json.encode({
"themeStyle": ApplicationConstant.darkAppTheme
});
preferences.setString(ApplicationConstant.sharedTheme, currentThemeInfo);
return _themeData;
}
else {
final extractedThemeInfo = json.decode(preferences.getString(ApplicationConstant.sharedTheme)) as Map<String, dynamic>;
final chosenTheme = extractedThemeInfo["themeStyle"];
if (chosenTheme == ApplicationConstant.lightAppTheme) {
_themeData = appThemeDataLight;
return _themeData;
}
else if (chosenTheme == ApplicationConstant.darkAppTheme) {
_themeData = appThemeDataDark;
return _themeData;
}
else {
_themeData = appThemeDataDark; //Its better to define a third theme style, something like appThemeDefault, but in order not to spend more time on dummy stuff, I skip that part
return _themeData;
}
}
}
}
Now, as you can see, ThemeChanger class is no longer expecting a ThemeData manually, but setting it automatically whenever its called as setInitialTheme() method is assigned to its constructor. And, of course, MyApp in main.dart is changed accordingly:
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return MultiProvider(
providers: [
//Several ChangeNotifierProviders
],
child: Consumer<AuthenticateProvider>(
builder: (ctx, authData, _) => ChangeNotifierProvider<ThemeChanger>(
create: (_) => ThemeChanger(),
child: _MaterialAppWithTheme(authData),
)
)
);
}
}
Now, app is launching just fine with the last selected ThemeData which has a pointer stored in SharedPreferences.