I'm currently using Provider as state management and also to keep all my function in it. At first i was using a callback method for me to to navigate thru screen when function in my Provider class succeed.
Future login(String email, String password, Function callback) async {
_isLoading = true;
notifyListeners();
bool isSuccess = false;
try {
final ApiResponse apiResponse = await authRepo!.login(email, password);
if (apiResponse.response != null && apiResponse.response!.statusCode == 200) {
isSuccess = true;
callback(isSuccess, apiResponse.response!.data[Constants.responseMsg]);
} else {
callback(isSuccess, apiResponse.error);
}
} catch (e) {
_isLoading = false;
print('login error: $e');
notifyListeners();
rethrow;
}
_isLoading = false;
notifyListeners();
}
but then i realized i could just pass the Buildcontext and navigating inside the function itself without using a callback method.
Future login(String email, String password, BuildContext context) async {
_isLoading = true;
notifyListeners();
try {
final ApiResponse apiResponse = await authRepo!.login(email, password);
if (apiResponse.response != null && apiResponse.response!.statusCode == 200) {
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (BuildContext context) => DashboardScreen(),
settings: RouteSettings(name: '/Dashboard'),
),
);
} else {
GlobalFunction.showToast(apiResponse.error);
}
} catch (e) {
_isLoading = false;
print('login error: $e');
notifyListeners();
rethrow;
}
_isLoading = false;
notifyListeners();
}
and then i also realize i could use NavigatorKey.currentState!.context to navigate so i dont need the pass the Buildcontext.
Future login(String email, String password) async {
_isLoading = true;
notifyListeners();
try {
final ApiResponse apiResponse = await authRepo!.login(email, password);
if (apiResponse.response != null && apiResponse.response!.statusCode == 200) {
BuildContext _context = navigatorKey.currentState!.context;
Navigator.of(_context).pushReplacement(
MaterialPageRoute(
builder: (BuildContext context) => DashboardScreen(),
settings: RouteSettings(name: '/Dashboard'),
),
);
} else {
GlobalFunction.showToast(apiResponse.error);
}
} catch (e) {
_isLoading = false;
print('login error: $e');
notifyListeners();
rethrow;
}
_isLoading = false;
notifyListeners();
}
i wonder which one is the better way?
Related
I'm trying to build a funtion who return Widget for persiting state but I am getting this error.
Future<Widget?> persiting () async {
await FirebaseAuth.instance
.authStateChanges()
.listen((User? user) {
if (user == null) {
return SignUpScreen() ;
} else {
return HomeScreen() ;
}
});
}
try this:
Future<Widget?> persiting () async {
late bool hasUsers;
await FirebaseAuth.instance
.authStateChanges()
.listen((User? user) {
if (user == null) {
hasUsers = true;
} else {
hasUsers = false;
}
});
return hasUsers ? SignUpScreen() : HomeScreen();
}
The exception showed because you are returning onject to the .listen() function instead of the persiting() function.
What you need to do is await to listen the stream inside the presiting() function.
Future<Widget?> persiting () async {
try{
Stream<User?> stream = await FirebaseAuth.instance
.authStateChanges();
bool hasUser = false;
await stream.forEach((user){
hasUser = user != null;
});
return hasUser? HomeScreen() : SignUpScreen();
}catch(e){
/// better do some handling if any network or unexpected error here
}
}
I am using a provider package. I want to display a loading spinner while waiting for a request to complete. The pattern below is too verbose. Please help me make it less verbose. Here is my code
class APIService with ChangeNotifier {
// Check for working API backend
bool isWorking = false;
bool isLoading = false;
set _isLoading(bool value) {
isLoading = value; <--
notifyListeners();
}
Future<bool> selectAPI(String input) async {
_isLoading = true; <-- 1
final uri = Uri.tryParse('https://$input$url')!;
final response = await http.get(uri);
if (response.statusCode == 200) {
final body = jsonDecode(response.body) as Map<String, dynamic>;
bool isTrue = body['info']['title'] == 'SamFetch';
_isLoading = false; <-- 2
notifyListeners();
return isWorking = isTrue;
}
_isLoading = false; <-- 3
throw response;
}
}
Here is my UI code
IconButton(
icon: apiService.isLoading
? CircularProgressIndicator()
: Icon(Icons.done),
onPressed: () async {
await addAPI(apiService, cache);
}),
}
Below is addAPI() method
Future<void> addAPI(APIService apiService, Cache cache) async {
if (api != null) {
try {
await apiService.selectAPI(api!);
if (apiService.isWorking) {
await cache.saveAppName(api!);
}
} on SocketException catch (e) {
print(e);
} catch (e) {
await cache.clearCache();
}
}
}
Is setState the final solution?
You can use Future Builder and set your Future Function in future attribute. You can control the visible widget based on the status of your function. So you dont have to use isloading variable.
I am trying to use this plugin https://pub.dev/packages/connectivity/example Issue is its not showing or print internet is connected or not.
This is my code
class _HomePageState extends State<HomePage> {
String _connectionStatus = 'Unknown';
final Connectivity _connectivity = Connectivity();
StreamSubscription<ConnectivityResult> _connectivitySubscription;
#override
void initState() {
super.initState();
initConnectivity();
_connectivitySubscription =
_connectivity.onConnectivityChanged.listen(_updateConnectionStatus);
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
if (mounted) {
if (userManager.getCurrentDriver() != null &&
userManager.getCurrentDriver().isNotEmpty) {
FirebaseFirestore.instance
.collection(FIREBASE_PATH_TRIP)
.doc(userManager.getCurrentDriver())
.get()
.then((event) {
if (event != null) {
var trip =
DriverModel.fromMap(Map<String, dynamic>.from(event.data()));
Provider.of<TripState>(context, listen: false).driver = trip;
Provider.of<BottomSheetSelector>(context, listen: false)
.changeSheet(SheetType.Profile);
} else {
userManager.saveCurrentDriver('');
}
});
}
if (Theme.of(context).platform == TargetPlatform.android) {
checkForAndroidUpdate(context);
}
}
});
}
#override
void dispose() {
_connectivitySubscription.cancel();
super.dispose();
}
Future<void> initConnectivity() async {
ConnectivityResult result;
// Platform messages may fail, so we use a try/catch PlatformException.
try {
result = await _connectivity.checkConnectivity();
} on PlatformException catch (e) {
print(e.toString());
}
// 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 Future.value(null);
}
return _updateConnectionStatus(result);
}
#override
Widget build(BuildContext context) {
final _drawerKey = GlobalKey<ScaffoldState>();
ScreenUtil.init(context);
return SafeArea(
child: WillPopScope(
child: Scaffold(
key: _drawerKey,
backgroundColor: Colors.black,
resizeToAvoidBottomInset: false,
drawer: ViteDrawer(),
body: null,
),
));
}
Future<void> _updateConnectionStatus(ConnectivityResult result) async {
switch (result) {
case ConnectivityResult.wifi:
case ConnectivityResult.mobile:
case ConnectivityResult.none:
setState(() => _connectionStatus = result.toString());
break;
default:
setState(() => _connectionStatus = 'Failed to get connectivity.');
break;
}
}
}
What i need to do is simple print if internet is connected or not. I want to show alert but print is ok so ill manage it. But dont know why its not printing anything
You can try with this
Future<bool> check() async {
var connectivityResult = await (Connectivity().checkConnectivity());
if (connectivityResult == ConnectivityResult.mobile) {
print("Connected}");
return true;
} else if (connectivityResult == ConnectivityResult.wifi) {
print("Connected}");
return true;
}
print("not Connected}");
// return You can add your dialog for notify user to your connectivity is off
}
you can use below code to check the connectivity
Future<bool> checkInternetConnectivity() async {
try {
final result = await InternetAddress.lookup('google.com');
if (result.isNotEmpty && result[0].rawAddress.isNotEmpty) {
return true;
} else {
return false;
}
} on SocketException catch (_) {
return false;
}
}
simple
Future<bool> isConnected() async {
var result = await Connectivity().checkConnectivity();
return result != ConnectivityResult.none;
}
I've been trying a sample Flutter application code from GitHub to simply login and register the user on Firebase. Every time I login or register after clean building the application, it takes me to the main page but throws this exception Exception has occurred. NoSuchMethodError (NoSuchMethodError: Class 'FlutterError' has no instance getter 'code'. Receiver: Instance of 'FlutterError' Tried calling: code)
I've no idea what 'FlutterError' is referring to because I don't see any such class. and there are two occurrences of code in the file named 'login-register.dart'. I'm attaching the code below:
(Note: it runs okay after I hot reload the app and the user is already logged in, only throws exception the first time)
void _validateLoginInput() async {
final FormState form = _formKey.currentState;
if (_formKey.currentState.validate()) {
form.save();
_sheetController.setState(() {
_loading = true;
});
try {
final FirebaseUser user = (await FirebaseAuth.instance
.signInWithEmailAndPassword(email: _email, password: _password)).user;
// final uid = user.uid;
Navigator.of(context).pushReplacementNamed('/home');
} catch (error) {
switch (error.code) {
case "ERROR_USER_NOT_FOUND":
{
_sheetController.setState(() {
errorMsg =
"There is no user with such entries. Please try again.";
_loading = false;
});
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
content: Container(
child: Text(errorMsg),
),
);
});
}
break;
case "ERROR_WRONG_PASSWORD":
{
_sheetController.setState(() {
errorMsg = "Password doesn\'t match your email.";
_loading = false;
});
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
content: Container(
child: Text(errorMsg),
),
);
});
}
break;
default:
{
_sheetController.setState(() {
errorMsg = "";
});
}
}
}
} else {
setState(() {
_autoValidate = true;
});
}
}
void _validateRegisterInput() async {
final FormState form = _formKey.currentState;
if (_formKey.currentState.validate()) {
form.save();
_sheetController.setState(() {
_loading = true;
});
try {
final FirebaseUser user = (await FirebaseAuth.instance
.createUserWithEmailAndPassword(
email: _email, password: _password)).user;
UserUpdateInfo userUpdateInfo = new UserUpdateInfo();
userUpdateInfo.displayName = _displayName;
user.updateProfile(userUpdateInfo).then((onValue) {
Navigator.of(context).pushReplacementNamed('/home');
Firestore.instance.collection('users').document().setData(
{'email': _email, 'displayName': _displayName}).then((onValue) {
_sheetController.setState(() {
_loading = false;
});
});
});
} catch (error) {
switch (error.code) {
case "ERROR_EMAIL_ALREADY_IN_USE":
{
_sheetController.setState(() {
errorMsg = "This email is already in use.";
_loading = false;
});
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
content: Container(
child: Text(errorMsg),
),
);
});
}
break;
case "ERROR_WEAK_PASSWORD":
{
_sheetController.setState(() {
errorMsg = "The password must be 6 characters long or more.";
_loading = false;
});
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
content: Container(
child: Text(errorMsg),
),
);
});
}
break;
default:
{
_sheetController.setState(() {
errorMsg = "";
});
}
}
}
} else {
setState(() {
_autoValidate = true;
});
}
}
The exception you're catching doesn't have a code property. That only exists with the firebase exception implementation, not the general exception class.
If you expect a certain type of error, you should explicitly catch that error and handle it properly and have a separate catch block for all other errors.
This can be done with an on ... catch block:
try {
final FirebaseUser user = (await FirebaseAuth.instance
.signInWithEmailAndPassword(email: _email, password: _password)).user;
// final uid = user.uid;
Navigator.of(context).pushReplacementNamed('/home');
} on FirebaseAuthException catch (error) {
...
} catch(e) {
...
}
The methods you're calling in the code you shared will throw FirebaseAuthExceptions as shown in the code above.
You are getting an error that is not a FirebaseError but a FlutterError. This means, it does not implement a code field.
You can simply put
if(!(error is FirebaseError)){
print(error.message); // this is the actual error that you are getting
}
right below catch(error) { (in both files) to handle this.
However, it seems like you get another Flutter Error that you might want to handle. It should be printed to the console now.
I have a basic login form, with my LoginModel.
But I do not understand how I can call to the function notifyListeners to display a dialog in my view.
The login widget:
#override
Widget build(BuildContext context) {
return new Scaffold(
body: new ScopedModel<LoginModel>(
model: _loginModel,
child: Center(child: ScopedModelDescendant<LoginModel>(
builder: (context, child, model) {
if (model.status == Status.LOADING) {
return Loading();
}
else return showForm(context);
}))));
}
And the login model:
class LoginModel extends Model {
Status _status = Status.READY;
Status get status => _status;
void onLogin(String username, String password) async {
_status = Status.LOADING;
notifyListeners();
try {
await api.login();
_status = Status.SUCCESS;
notifyListeners();
} catch (response) {
_status = Status.ERROR;
notifyListeners();
}
}
I need to display a dialog when the status is Error
Finally I got this, just returning a Future in the method onLogin
Future<bool> onLogin(String username, String password) async {
_status = Status.LOADING;
notifyListeners();
try {
await api.login();
_status = Status.SUCCESS;
notifyListeners();
return true;
} catch (response) {
_status = Status.ERROR;
notifyListeners();
return false;
}
}
And in the widget:
onPressed: () async {
bool success = await _loginModel.onLogin(_usernameController.text, _passwordController.text);
if(success) {
Navigator.pop(context, true);
}
else{
_showDialogError();
}
}