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();
}
}
Related
I am new to Flutter and not sure if this is the right method to follow. I am following YouTube tutorial to create a movie app using TMDB API and trying to pass 'genreId' from category.dart(comment below) to MovieBloc.dart. Is it possible to send the 'genreId'? If yes, how can I pass it? If not, what is the best way to do it?
Thank you
category.dart
final movieBloc = MovieBloc();
setState(() {
selectedGenre = genre.id;
movieBloc.eventSink.add(MovieControlAction.byGenre); // send genreId through here??
});
movie_bloc.dart
import 'dart:async';
import 'package:flutter_api/Model/movie.dart';
import 'package:flutter_api/Service/api_service.dart';
enum MovieControlAction { fetch, delete, byGenre, }
class MovieBloc {
final service = ApiService();
final _stateStreamController = StreamController<List<Movie>>();
StreamSink<List<Movie>> get _movieSink => _stateStreamController.sink;
Stream<List<Movie>> get movieStream => _stateStreamController.stream;
final _eventStreamController = StreamController<MovieControlAction>();
StreamSink<MovieControlAction> get eventSink =>
_eventStreamController.sink; //input
Stream<MovieControlAction> get _eventStream =>
_eventStreamController.stream; //output
MovieBloc() {
_eventStream.listen((event) async {
if (event == MovieControlAction.fetch) {
try {
var movies = await service.getNowPlayingMovie();
if (movies != null) {
_movieSink.add(movies);
} else {
_movieSink.addError('Null');
}
} on Exception catch (e) {
print(e);
_movieSink.addError('Something went wrong');
}
} else if (event == MovieControlAction.byGenre) {
var moviesByGenre = await service.getMovieByGenre(genreId);
if (moviesByGenre != null) {
_movieSink.add(moviesByGenre);
} else {
_movieSink.addError('Null');
}
}
});
}
void dispose() {
_stateStreamController.close();
_eventStreamController.close();
}
}
You can pass it to the MovieBloc like follows:
final movieBloc = MovieBloc(genreId: genre.id);
And then create a constructor within the MovieBloc class:
class MovieBloc {
final String genreId; // String, int, whatever type it is
MovieBloc({
required this.genreId,
});
}
If you want to use a real Bloc, you should extend the MovieBloc like this:
class MovieBloc extends Bloc<MovieEvent, MovieState> {}
Currently it's not looking like you are using the actual Bloc pattern. Please check the official Bloc documentation. I promise - it's super helpful.
I am trying to refactor my code and use a separate class object to register users and then send them to the correct page which is either the awaiting for email confirmation screen(if they haven't confirmed their email address) or the actual chat screen in the app.
I pass the Navigator.pushNamed object from the Flutter material build to the separate class object, but the Navigator.pushNamed doesn't work when it is sent as a parameter to the class object. It only worked when it is in the main build.
This is the main section that collects the parameters and sends them to the class
Widget build(BuildContext context) {...
Registration(
email: email,
password: password,
awaitingEmailConfPage:
() async {
await Navigator.pushNamed(context, EmailConfirmation.id);
},
confirmedEmailConfPage: () async {
await Navigator.pushNamed(context, ChatScreen.id);
}
).registerNewUser();
This is the class object that receives the Navigator.pushNamed but doesn't want to allow it to work and send users to the correct screen.
class Registration {
Registration(
{required this.awaitingEmailConfPage,
required this.confirmedEmailConfPage});
final _auth = FirebaseAuth.instance;
Future Function() awaitingEmailConfPage;
Future Function() confirmedEmailConfPage;
void registerNewUser() async {
try {
User? user = _auth.currentUser;
if (user!.emailVerified) {
await awaitingEmailConfPage;
} else {
await confirmedEmailConfPage;
}
}
}
If you want to call a function that receive as parameter you need to use call() method:
if (user!.emailVerified) {
await awaitingEmailConfPage.call();
} else {
await confirmedEmailConfPage.call();
}
class UserProvider extends ChangeNotifier {
UserLoginModel userLoginModel = UserLoginModel();
UserLoginModel get userdata {
return userLoginModel;
}
void addlogindata(UserLoginModel data) {
userLoginModel = data;
notifyListeners();
}
}
How to set data to this class using api or UserLoginModel through function
i want to set data like this
if (response.statusCode == 200) {
print(response.body);
var data = UserLoginModel.fromJson(jsonDecode(response.body)); userprovider.addlogindata(data);
bur i am getting error by doing this kind of thing
you can call the function like this within in the build method:
Provider.of<UserProvider>(context).addlogindata(data)
outside the build method:
Provider.of<UserProvider>(context,listen:false).addlogindata(data)
var data=Provider.of<UserProvider>(context,listen:false);
var newdata=data.addlogindata(responsedata);
that work perfectly for me and now i can use that data anywhere in the app
I'm trying to use riverpod for login with a laravel backend. Right now I'm just returning true or false from the repository. I've set a form that accepts email and password. The isLoading variable is just to show a circle indicator. I've run the code and it works but not sure if I'm using riverpod correctly. Is there a better way to do it ?
auth_provider.dart
class Auth{
final bool isLogin;
Auth(this.isLogin);
}
class AuthNotifier extends StateNotifier<Auth>{
AuthNotifier() : super(Auth(false));
void isLogin(bool data){
state = new Auth(data);
}
}
final authProvider = StateNotifierProvider((ref) => new AuthNotifier());
auth_repository.dart
class AuthRepository{
static String url = "http://10.0.2.2:8000/api/";
final Dio _dio = Dio();
Future<bool> login(data) async {
try {
Response response = await _dio.post(url+'sanctum/token',data:json.encode(data));
return true;
} catch (error) {
return false;
}
}
}
login_screen.dart
void login() async{
if(formKey.currentState.validate()){
setState((){this.isLoading = true;});
var data = {
'email':this.email,
'password':this.password,
'device_name':'mobile_phone'
};
var result = await AuthRepository().login(data);
if(result){
context.read(authProvider).isLogin(true);
setState((){this.isLoading = false;});
}else
setState((){this.isLoading = false;});
}
}
Since I'm not coming from mobile background and just recently use flutter+riverpod in my recent project, I cannot say this is the best practice. But there are some points I'd like to note:
Use interface such IAuthRepository for repository. Riverpod can act as a dependency injection.
final authRepository = Provider<IAuthRepository>((ref) => AuthRepository());
Build data to send in repository. You should separate presentation, business logic, and explicit implementation for external resource if possible.
Future<bool> login(String email, String password) async {
try {
var data = {
'email': email,
'password': password,
'device_name':'mobile_phone'
};
Response response = await _dio.post(url+'sanctum/token',data:json.encode(data));
return true;
} catch (error) {
return false;
}
}
Do not call repository directly from presentation/screen. You can use the provider for your logic, which call the repository
class AuthNotifier extends StateNotifier<Auth>{
final ProviderReference ref;
IAuthRepository _authRepository;
AuthNotifier(this.ref) : super(Auth(false)) {
_authRepository = ref.watch(authRepository);
}
Future<void> login(String email, String password) async {
final loginResult = await_authRepository.login(email, password);
state = Auth(loginResult);
}
}
final authProvider = StateNotifierProvider((ref) => new AuthNotifier(ref));
On screen, you can call provider's login method
login() {
context.read(authProvider).login(this.email, this.password);
}
Use Consumer or ConsumerWidget to watch the state and decide what to build.
It also helps that instead of Auth with isLogin for the state, you can create some other state. At the very least, I usually create an abstract BaseAuthState, which derives to AuthInitialState, AuthLoadingState, AuthLoginState, AuthErrorState, etc.
class AuthNotifier extends StateNotifier<BaseAuthState>{
...
AuthNotifier(this.ref) : super(AuthInitialState()) { ... }
...
}
Consumer(builder: (context, watch, child) {
final state = watch(authProvider.state);
if (state is AuthLoginState) ...
else if (state is AuthLoadingState) ...
...
})
Instead of using a bool, I like to use enums or class for auth state
enum AuthState { initialize, authenticated, unauthenticated }
and for login state
enum LoginStatus { initialize, loading, success, failed }
I am trying to test my catch in my signInAnonyymous() function. Yet, I am constantly getting the error stated in the title.
Here is my Mocked Auth class (edited for brevity):
class MockFirebaseAuth extends Mock implements FirebaseAuth {
final bool signedIn;
final bool isEmployee;
MockFirebaseAuth({this.isEmployee = false, this.signedIn = false});
#override
Future<UserCredential> signInAnonymously() async {
return MockUserCredential(isAnonymous: true);
}
}
And, this is my test:
group('Sign-In-Anonymously', () {
setUp(() {
mockFunctions = MockFirebaseFunctions();
mockAuth = MockFirebaseAuth();
mockCrashltyics = MockCrashlytics();
mockFirestore = MockFirebaseFirestore();
authRemoteService = AuthServiceFirebase(
crashlytics: mockCrashltyics,
firestoreService: mockFirestore,
functions: mockFunctions,
service: mockAuth,
);
authProvider = AuthProvider(authRemoteService);
});
test(
'Should return a null AuthUser and non-null errorMessage on exception thrown',
() async {
when(mockAuth.signInAnonymously())
.thenThrow(MockFirebaseAuthException('message', 'code'));
//act
await authProvider.signInAnonymously();
//assert
expect(authProvider.authUser, isNull);
expect(authProvider.errorMessage, isNotNull);
});
});
I have researched and tried so many different ways and still can't test my catch. Any help would be appreciated. Thank you in advance.
If you're going to use 'when', you can't override the method in the mock. You need to provide an answer from the "thenAnswer".