To make my point clear I have created this small code..
it looks like I missing to understand concept of byref and byval here..
and what does actually Get.put works.
I have commented the lines what made me confused
GetxController
class UserController extends GetxController {
List<UserModel> _userlist = [];
UserModel? currentuser;
UserController() {
_userlist.add(UserModel(username: 'temp', password: 't123'));
}
List<UserModel> get getusers {
return _userlist;
}
}
this is main
void main()
{
final uc1=Get.put(UserController());
UserModel user1=UserModel(username: 'abc', password: 'a123');
uc1.currentuser=user1;
//showing print abc
print(uc1.currentuser!.username);
final uc2=Get.put(UserController());
UserModel user2=UserModel(username: 'xyz', password: 'x123');
uc2.currentuser=user2;
//showing printing result=xyz
print(uc2.currentuser!.username);
//showing printing result=xyz, instead of abc why?
print(uc1.currentuser!.username);
}
Get.put only puts a new instance when there isn't already one, (or the one that's there is marked as dirty). You can see it by digging deeper into the code. Ctrl + clicking the put method (in Android Studio at least) makes you able to see the implementation. The first step leads to
S put<S>(S dependency,
{String? tag,
bool permanent = false,
InstanceBuilderCallback<S>? builder}) =>
GetInstance().put<S>(dependency, tag: tag, permanent: permanent);
then doing it on put again leads to
S put<S>(
S dependency, {
String? tag,
bool permanent = false,
#deprecated InstanceBuilderCallback<S>? builder,
}) {
_insert(
isSingleton: true,
name: tag,
permanent: permanent,
builder: builder ?? (() => dependency));
return find<S>(tag: tag);
}
And digging into the _insert method gives:
/// Injects the Instance [S] builder into the `_singleton` HashMap.
void _insert<S>({
bool? isSingleton,
String? name,
bool permanent = false,
required InstanceBuilderCallback<S> builder,
bool fenix = false,
}) {
final key = _getKey(S, name);
if (_singl.containsKey(key)) {
final dep = _singl[key];
if (dep != null && dep.isDirty) {
_singl[key] = _InstanceBuilderFactory<S>(
isSingleton,
builder,
permanent,
false,
fenix,
name,
lateRemove: dep as _InstanceBuilderFactory<S>,
);
}
} else {
_singl[key] = _InstanceBuilderFactory<S>(
isSingleton,
builder,
permanent,
false,
fenix,
name,
);
}
}
Here you can see that if it already exists and isDirty is true, or if it doesn't exist yet, only then it inserts a new instance. Now I'm not entirely sure when isDirty is set, but I believe it happens when you change routes in your app. So the second time you call put you are actually retrieving the one that is already put there before. Now if you want to have multiple instances you can use Get.create(() => UserController()); followed by Get.find instead. Like:
void main()
{
Get.create(() => UserController());
UserController uc1 = Get.find();
UserModel user1=UserModel(username: 'abc', password: 'a123');
uc1.currentuser=user1;
//showing print abc
print(uc1.currentuser!.username);
UserController uc2 = Get.find();
UserModel user2=UserModel(username: 'xyz', password: 'x123');
uc2.currentuser=user2;
//showing printing result=xyz
print(uc2.currentuser!.username);
//showing printing result=abc
print(uc1.currentuser!.username);
}
Because you are using the put method two time
if you want to update a single instance then follow the below code:
void main()
{
final uc1=Get.put(UserController());
UserModel user1=UserModel(username: 'abc', password: 'a123');
uc1.currentuser=user1;
//showing print abc
print(uc1.currentuser!.username);
UserController uc2 = Get.find();
UserModel user2=UserModel(username: 'xyz', password: 'x123');
uc2.currentuser=user2;
//showing printing result=xyz
print(uc2.currentuser!.username);
//showing printing result=abc
print(uc1.currentuser!.username);
}
Related
I am listening to my Riverpods provider in my build method like so:
ref.listen<UserState>(userProvider, (UserState? prevState, UserState newState) {
print("LISTEN CALLED");
}
What I think should happen:
Whenever I update my UserState object with a .CopyWith(), "LISTEN CALLED" should be printed.
What actually happens:
"LISTEN CALLED" is only printed once at the start of my program, and then even though the UserState's properties change, the listen() is not called again.
Here is the call that updates my UserState (from a button):
onTap: () {
ref.read(userProvider.notifier).logout();
}
This does a call to my database and device storage, and then executes this line of code:
return state = state.copyWith(
newError: true, newLoading: false, newAccessToken: "", newLoggedIn: false);
This state is different than it is before the logout button is pressed (for example, loggedIn is true and accessToken has a value) - view code below to know what those are.
Here are the actual classes that use Riverpods, first up: UserState:
#immutable
class UserState {
const UserState({
this.error = false,
this.accessToken = "",
this.loading = true,
this.loggedIn = false,
});
final bool error;
final String accessToken;
final bool loading;
final bool loggedIn;
UserState copyWith(
{String? newAccessToken, bool? newError, bool? newLoading, bool? newLoggedIn}) {
return UserState(
error: newError ?? error,
accessToken: newAccessToken ?? accessToken,
loading: newLoading ?? loading,
loggedIn: newLoggedIn ?? loggedIn,
);
}
}
Next, here's my UserNotifier class:
class UserNotifier extends StateNotifier<UserState> {
UserNotifier() : super(const UserState());
void logout() {
// ... does lots of calls to database and device storage, then:
return state =
state.copyWith(newError: true, newLoading: false, newAccessToken: "", newLoggedIn: false);
}
}
Finally, here is my provider:
final userProvider = StateNotifierProvider<UserNotifier, UserState>((ref) {
return UserNotifier();
});
So, my problem is that everytime I call this logout() method (like from the button) my state CHANGES, but my listen() function from Riverpods is not called
Below code is NOT CALLED when my state changes through any method (like my logout() one), but I need it to:
ref.listen<UserState>(userProvider, (UserState? prevState, UserState newState) {
print("LISTEN CALLED");
}
Any help would be massively appreciated!!
I think the problem is with the return in the logout method.
Try:
void logout() {
// ... does lots of calls to database and device storage, then:
state =
state.copyWith(newError: true, newLoading: false, newAccessToken: "", newLoggedIn: false);
}
I use Riverpod for state management
I'd like to observe a String message which is shared by a StateNotifier<LoginNotifierData>. I call ref.listen in a ConsumerState<LoginScreen>. But although state.message is being changed, the Function in ref.listen is not called. My question is why the function which is given as the second parameter for ref.listen() is not called when the state is being changed.
I use Equatable library to compare objects
When I observe the whole LoginNotifierData object (without loginModelStateNotifierProvider.select()) it also doesn't work
login method in Notifier (it changes the state):
void login(String username, String password, WidgetRef ref) async {
state.message = "Logowanie...";
Either<Failure, LoginModel> setLogin = await ref
.read(loginUseCaseProvider)
.call(LoginParams(
username: username,
password: password));
setLogin.fold((failure) {
log('API Login: failure');
state.message = failure.message;
log(failure.toString());
}, (loginModel) {
log('API Login: success=${loginModel.success}');
if (loginModel.success) {
ref
.read(loginModelStateNotifierProvider.notifier)
.setLoginModel(loginModel);
_storeUserData(loginModel, ref);
ref.read(userRepositoryProvider).storeUsername(username);
ref.read(userRepositoryProvider).storePassword(password);
state.message = "";
} else {
state.message = loginModel.message;
log('responseCode: ${loginModel.responseCode}');
}
});
}
LoginNotifierData (the state is of this type)
class LoginNotifierData extends Equatable {
LoginModel loginModel = LoginModel("", 0, "", false, "", "", "", false, 0.0, "", "");
String message = "";
LoginNotifierData(this.loginModel, this.message);
#override
List<Object?> get props => [loginModel, message];
}
ref.listen called in #override Widget build method in a ConsumerState
ref.listen<String>(
loginModelStateNotifierProvider.select((value) => value.message),
(_, message) {
if(message.isNotEmpty) {
widget.showLoaderDialog(context, message);
} else {
widget.hideDialog();
}
}
);
loginModelStateNotifierProvider:
final loginModelStateNotifierProvider =
StateNotifierProvider<LoginModelStateNotifier, LoginNotifierData>(
(ref) => LoginModelStateNotifier()
);
You can't update the a state by re-assigning properties in a StateNotifier...You have to re-assign the state itself.
Using copyWith method allows you to do this.
Follow these steps:
1. Create a copyWith method in LoginNotifierData
class LoginNotifierData extends Equatable {
LoginModel loginModel = LoginModel("", 0, "", false, "", "", "", false, 0.0, "", "");
String message = "";
LoginNotifierData(this.loginModel, this.message);
LoginNotifierData copyWith({LoginModel? loginModel, String? message}){
return LoginNotifierData(
loginModel: loginModel,
message: message,
);
}
#override
List<Object?> get props => [loginModel, message];
}
2. Use the copy with method to re-assign the state in the StateNotifier
void login(String username, String password, WidgetRef ref) async {
state = state.copyWith(message: "Logowanie...");
Either<Failure, LoginModel> setLogin = await ref
.read(loginUseCaseProvider)
.call(LoginParams(
username: username,
password: password));
setLogin.fold((failure) {
log('API Login: failure');
state = state.copyWith(message: failure.message);
log(failure.toString());
}, (loginModel) {
log('API Login: success=${loginModel.success}');
if (loginModel.success) {
ref
.read(loginModelStateNotifierProvider.notifier)
.setLoginModel(loginModel);
_storeUserData(loginModel, ref);
ref.read(userRepositoryProvider).storeUsername(username);
ref.read(userRepositoryProvider).storePassword(password);
state = state.copyWith(message: "");
} else {
state = state.copyWith(message: loginModel.message);
log('responseCode: ${loginModel.responseCode}');
}
});
}
I have this Getx controller for reading content of a Post from database:
class ReadSinglePostController extends GetxController {
var isLoading = true.obs;
var posts = Post(
postID: 1,
userID: 0,
thumbnail: 'thumbnail',
imageList: 'imageList',
title: 'title',
description: 'description',
createdTime: DateTime.now())
.obs; //yes this can be accessed
var postid = 2.obs; //I want this value to change when I click a post in the UI
#override
void onInit() {
super.onInit();
readPost(postid);
}
updateID(var postID) {
postid.value = postID;
print('im print ${postid.value}');
}//should update postid when a post is clicked in the UI
Future readPost(var postID) async {
try {
isLoading(true);
var result = await PostsDatabase.instance.readPost(postID);
posts.value = result;
} finally {
isLoading(false);
}
}
}
But the problem I'm now facing is that: to read a specific Post from database, I need the postID parameter. And as you can imagine, this parameter can be recorded when I click a specific Post in UI, but how do I pass that parameter to this Getx controller? Or maybe I am doing this whole thing wrong?
You can use the instance of your controller on the Ui.
For example, on the widget you call the controller:
final ReadSinglePostController _controller = Get.put(ReadSinglePostController());
//and when you need to change you do like this:
_controller.updateID(newId);
Inside the updateID method you can call the load method:
updateID(var postID) {
postid.value = postID;
print('im print ${postid.value}');
readPost(postID);
}
For those who may be using obs, you can do as follows:
In the controller, you can define
var postid = 0.obs
and from the view just add
controller.postid.value = 20;
I am using Get (getx) package to manage the state of my app, and I am trying to use a controller inside another one. For example I have a class the contains methods for firebase authentication
class FirebaseAuthController extends GetxController {
static FirebaseAuthController get to => Get.find<FirebaseAuthController>();
.....
Future<void> createUser(String email, String password) async {
try {
await _auth.createUserWithEmailAndPassword(
email: email, password: password);
} catch (e) {
...
}
}
...
...
}
and I have another controller which is signUpController that interacts with the UI
class SignInController extends GetxController {
static SignInController get to => Get.find<SignInController>();
...
....
Future<void> clickSignInButton() async {
print(emailController.text);
print(passwordController.text);
if (formKey.currentState.validate()) {
try {
await FirebaseAuthController.to
.login(emailController.text, passwordController.text);
} catch (e) {
print(e);
}
}
}
}
when I try to do this, it gives me an error
lib/screens/authentication_screens/controller/sign_up_controller.dart:56:37: Error: Getter not found: 'to'.
await FirebaseAuthController.to
any idea what might be the issue?
You absolutely can, despite it's considered a bad practice. It's recommended to use something like repository or usecase classes instead unless you want to update the data shown on the screen attached to that controller from another controller.
And for the solution to your actual problem or error, just change static FirebaseAuthController get to => Get.find<FirebaseAuthController>(); to static FirebaseAuthController get to => Get.put(FirebaseAuthController());
yes you can use one controller in another controller and user its variable and also update it
Example
class HomeController extends GetxController {
var home = '';
String userName = '';
updateName() {}
}
class LoginController extends GetxController {
HomeController homeController = Get.put(HomeController());
String email = '';
String password = '';
signin() {
email = homeController.userName;
homeController.updateName();
homeController.update();
}
}
Why we use sometimes notifyListeners and also why we do not use notifyListeners? How can we use a function in changenotifier ?
For instance, in this code, sometimes we used to notifyListeners, but sometimes we did not use notifyListeners (in login() function). Why ? When we use notifyListeners?
String get userEmail => _userEmail;
set userEmail(String value) {
_userEmail = value;
notifyListeners();
}
String get userPassword => _userPassword;
set userPassword(String value) {
_userPassword = value;
notifyListeners();
}
String get userName => _userName;
set userName(String value) {
_userName = value;
notifyListeners();
}
DateTime get dateOfBirth => _dateOfBirth;
set dateOfBirth(DateTime value) {
_dateOfBirth = value;
notifyListeners();
}
Future<bool> login() async {
try {
isLoading = true;
print(userEmail);
print(userPassword);
if (isLogin) {
await FirebaseAuth.instance.signInWithEmailAndPassword(
email: userEmail,
password: userPassword,
);
} else {
await FirebaseAuth.instance.createUserWithEmailAndPassword(
email: userEmail,
password: userPassword,
);
}
isLoading = false;
return true;
} catch (err) {
print(err);
isLoading = false;
return false;
}
}
}
Besides can someone answer me about why we use set method in this code
bool get isLogin => _isLogin;
set isLogin(bool value) {
_isLogin = value;
notifyListeners();
}
The method notifyListeners() is used whenever you want to trigger a rebuild or whenever there is an active listener which is monitoring for the change to perform certain actions
Assume a scenario, You have a data model, and as soon as you fetch the data from APIs you want to parse it and fill in the model class, Also you want that UI will automatically rebuild after getting the data !
For this purpose you can use notifyListeners() in data class and in your UI code, You can wrap the parts which needs to be rebuild with a ChangeNotifierProvider or a Consumer Widget that will monitor for changes and as soon as they encounter any change , They will rebuild the underlying Widget(s).
In the code you shared above, You've used private fields that cannot be accessed outside of this file and you are using getters and setters to basically retrieve them and modify them
Answering your last question now,
We use set method or setter to basically update or modify a value (generally a private value)
Just to provide you some additional info,
You should NOT wrap fields in getters and setters just to be "safe".
This method is Old and not needed in Dart language
You can read more about this here
You can also read about getters and setters in dart here.
Since you are new to StackOverflow, I welcome you.
Happy Fluttering !