SnackBar is not showing in flutter - flutter

AS I am new to flutter, I can't find why the SnackBar is not showing on my UI while I am calling different function for API call! In one case it is showing but not in other cases.
I have to show a Snackbar on success of each API call (like in my project it is on success of generateOtp and on success of verifyOtp).
Below is my code.
snackbar.dart
showInSnackBar(String message, key){
key.currentState.showSnackBar(
SnackBar(
content:Text(message),
backgroundColor: Colors.blueAccent[700],
)
);
}
api_service.dart
class ApiService {
bool isVerified = false;
BaseOptions options = BaseOptions(
baseUrl: "http://...",
);
generateOtp(String mobileNo, key) async {
Dio dio = new Dio(options);
FormData formData = FormData.fromMap({'mobile_no': mobileNo});
try {
Response response = await dio.post("generate_otp/", data: formData);
if (response.statusCode == 200) {
// on success of generate otp I have to show a message on SnackBar. But it is not working.
showInSnackBar(response.data["msg"], key);
print(response.data);
}
} on DioError catch (e) {
showInSnackBar(e.message, key);
}
}
Future<bool> verifyOtp(String mobileNo, String otp, key) async {
Dio dio = new Dio(options);
FormData formData = FormData.fromMap(
//.....);
try {
Response response = await dio.post("verify_otp/", data: formData);
if (response.statusCode == 200) {
// here also it is not working.
showInSnackBar(response.data["msg"], key);
// Otp verified
isVerified = true;
}
} on DioError catch (e) {
showInSnackBar(e.message, key);
}
return isVerified;
}
}
register.dart
class _RegisterPageState extends State<RegisterPage> {
var _key = new GlobalKey<ScaffoldState>();
//...........
service.generateOtp(_data.mobileNo, _key); /* here I am calling generateOtp() */
} else {
print('invalid credentials');
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
key: _key,
body: SingleChildScrollView(
//..........
otp.dart
submit() async {
_formKey.currentState.save();
bool verify =
await service.verifyOtp(widget.mobNumber, pinController.text, _key); /* here I am calling
verifyOtp() */
if (verify) {
SharedPreferences preferences = await SharedPreferences.getInstance();
String userInfo = preferences.getString('user_data');
// Decoding String data to map
Map map = json.decode(userInfo);
service.registerUser(map);
} else {
showInSnackBar('Invalid otp', _key); /* here SnackBar is showing on my UI*/
}
}
Can anybody please help me to solve this!

Lack of context, (Context).
docs : https://api.flutter.dev/flutter/widgets/BuildContext-class.html
try this(work for me):
void _showSnackBar(BuildContext context, String text) {
Scaffold.of(context).showSnackBar(SnackBar(content: Text(text)));
}

If You want to use snackbar without context u can use this package get: ^3.13.2
and call snackbar like this any where you want:
Get.snackbar(
"title",
"content",
);

Related

Flutter Secure Storage Issues: Unable to read or write keys and values

I am using this package to store some login credentials in a Flutter mobile application. The version I use is v5.0.2. I am not sure if I am storing or reading the value in the correct way. Does anyone know how to check it, or what am I missing or doing wrong.
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
class SecureStorage {
final _storage = const FlutterSecureStorage();
Future<Map<String, String>> _readAll() async {
return await _storage.readAll(
iOptions: _getIOSOptions(), aOptions: _getAndroidOptions());
}
void deleteAll() async {
await _storage.deleteAll(
iOptions: _getIOSOptions(), aOptions: _getAndroidOptions());
_readAll();
}
Future<String?> readSecureData(String key) async {
return await _storage.read(key: key);
}
Future<void> deleteSecureData(String key) async {
return await _storage.delete(key: key);
}
void writeSecureData(String key, String value) async {
await _storage.write(
key: key,
value: value,
iOptions: _getIOSOptions(),
aOptions: _getAndroidOptions(),
);
}
IOSOptions _getIOSOptions() => const IOSOptions(
accessibility: IOSAccessibility.first_unlock,
);
AndroidOptions _getAndroidOptions() => const AndroidOptions(
encryptedSharedPreferences: true,
);
}
final secureStorage = SecureStorage();
This is how I called the value,
#override
void initState() {
Future.delayed(Duration.zero, () async {
final username = await secureStorage.readSecureData('username') ?? '';
final password = await secureStorage.readSecureData('password') ?? '';
setState(() {
_icNoController.text = username;
_passwordController.text = password;
});
});
super.initState();
}
And this is how I stored the value,
await secureStorage.writeSecureData('username', username);
await secureStorage.writeSecureData('password', password);
I think the reason for reading / writing problem can be inconsistency with using aOptions or iOptions.
For ex. you are using aOptions with readAll(), deleateAll() and write() methods and you don't use it with read(), delete().
So, when using encryptedSharedPreferences: true, in aOptions when you write data to secure storage, you specify that this data must be written to EncryptedSharedPreferences.
However when you read the data without providing aOptions again, you try to read from default secure storage which is not the EncryptedSharedPreferences where you have stored the data.
Example with aOptions is for Android, I haven't tested this library on iOS yet.
I have cleaned up the class for you:
class SecureStorage {
const _storage = FlutterSecureStorage();
Future<Map<String, String>> _readAll() async {
var map = <String, String>{};
try {
map = await _storage.readAll(
iOptions: _getIOSOptions(),
aOptions: _getAndroidOptions(),
);
} catch (e) {
print(e);
}
return map;
}
Future<void> deleteAll() async {
try {
await _storage.deleteAll(
iOptions: _getIOSOptions(),
aOptions: _getAndroidOptions(),
);
// _readAll();
} catch (e) {
print(e);
}
}
Future<String> readSecureData(String key) async {
String value = "";
try {
value = (await _storage.read(key: key)) ?? "";
} catch (e) {
print(e);
}
return value;
}
Future<void> deleteSecureData(String key) async {
try {
await _storage.delete(key: key);
} catch (e) {
print(e);
}
}
Future<void> writeSecureData(String key, String value) async {
try {
await _storage.write(
key: key,
value: value,
iOptions: _getIOSOptions(),
aOptions: _getAndroidOptions(),
);
} catch (e) {
print(e);
}
}
IOSOptions _getIOSOptions() => const IOSOptions(
accessibility: IOSAccessibility.first_unlock,
);
AndroidOptions _getAndroidOptions() => const AndroidOptions(
encryptedSharedPreferences: true,
);
}
Of course, I use a Logger package instead of print. Also I don't understand why you do a _readAll after deleteAll.
ANSWERS
Q. "I am not sure if I am storing or reading the value in the correct way."
A. The correct way to store and read the values are by wrapping them in a try-catch block, as illustrated the code above.
Q. "Does anyone know how to check it, or what am I missing or doing wrong."
A. See the code above for example of how to do it the right way which I personally found works for me.

Display Loading spinner waitint for request to complete while using provider package

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.

Riverpod FutureProvider keeps on firiging again and again

I am using Riverpod's FutureProvider with family. The FutureProvider keeps on running again and again. It shows the loading dialog only. Also the hot reload stops working. FutureProvider is working fine without family. Please help in finding what's wrong.
final ephemerisProvider =
Provider((ref) => ApiService("https://localhost"));
final ephemerisFutureProvider = FutureProvider.family
.autoDispose<EpheModel, Map<String, dynamic>>((ref, data) async {
var response = await ref.read(ephemerisProvider).getData(data);
print(EpheModel.fromJSON(response));
return EpheModel.fromJSON(response);
});
class Kundlis extends ConsumerWidget {
static const routeName = "/kundlis";
#override
Widget build(BuildContext context, ScopedReader watch) {
final AsyncValue<EpheModel> kundlis = watch(ephemerisFutureProvider({}));
return Scaffold(
appBar: AppBar(
title: Text("Kundlis"),
),
drawer: AppDrawer(),
body: kundlis.when(
data: (kundli) => Center(child: Text(kundli.toString())),
loading: () => ProgressDialog(message: "Fetching Details..."),
error: (message, st) =>
CustomSnackBar.buildErrorSnackbar(context, '$message')));
}
}
class ApiService {
final String url;
ApiService(this.url);
Future<Map<String, dynamic>> getData(Map<String, dynamic> data) async {
try {
http.Response response = await http.post(url + "/ephe",
headers: <String, String>{'Content-Type': 'application/json'},
body: jsonEncode(data));
if (response.statusCode == 200) {
return data;
} else {
throw Exception("Error Fetching Details");
}
} on SocketException {
throw Exception("No Internet Connection");
} on HttpException {
throw Exception("Error Fetching Details");
}
}
}
{} != {}. Because of .family, you are creating a completely new provider every time you call watch(ephemerisFutureProvider({})). To select a previously-built provider via family, you must pass an identical value. And {} is never identical to {}, guaranteed. :)

How to get CONTEXT for the provider to work? Flutter

In the Future fetchStudentInfo() function, i would like to use the userId from my Auth class to do filtering. The userId is embedded in the URL and it will retrieve data from database. But, the issue is that the context is lacking in the function itself. However, I couldn't figure out a way to pass in the context. It would be great if any legend could help me. The solution which retrieve data from internet is found on the flutter documentation. And i wouldn't like to hard code the userId.
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:provider/provider.dart';
import '../model/student.dart';
import '../provider/auth.dart';
Future<Student> fetchStudentInfo() async {
final auth = Provider.of<Auth>(context);
final response = await http.post(
'https://intermediary-sharpe.000webhostapp.com/Student/read_one.php?userId=$auth.userId');
if (response.statusCode == 200) {
return Student.fromJson(json.decode(response.body));
} else {
throw Exception('Failed');
}
}
class ProfileScreen extends StatefulWidget {
#override
_ProfileScreenState createState() => _ProfileScreenState();
}
class _ProfileScreenState extends State<ProfileScreen> {
Future<Student> student;
#override
void initState() {
// TODO: implement initState
super.initState();
student = fetchStudentInfo();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder<Student>(
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text(snapshot.data.studentId);
} else if (snapshot.hasError) {
return Text('${snapshot.error}');
}
return CircularProgressIndicator();
},
future: student,
),
);
}
}
import 'package:flutter/cupertino.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'dart:async';
import 'package:shared_preferences/shared_preferences.dart';
import '../model/http_exception.dart';
class Auth with ChangeNotifier {
String _token;
DateTime _expiryDate;
String userId;
Timer _authTimer;
bool get isAuthenticated {
return token != null;
}
String get token {
if (_expiryDate != null &&
_expiryDate.isAfter(DateTime.now()) &&
_token != null) {
return _token;
}
return null;
}
Future<void> _authenticate(
String email, String password, String urlSegment) async {
final url =
'https://identitytoolkit.googleapis.com/v1/accounts:$urlSegment?key=AIzaSyCkNZysDY4PGpScw2jUlBpd0mvpGjgSEag';
try {
final response = await http.post(
url,
body: json.encode(
{
'email': email,
'password': password,
'returnSecureToken': true,
},
),
);
final responseData = json.decode(response.body);
if (responseData['error'] != null) {
throw HttpException(responseData['error']['message']);
}
_token = responseData['idToken'];
userId = responseData['localId'];
_expiryDate = DateTime.now().add(
Duration(
seconds: int.parse(
responseData['expiresIn'],
),
),
);
_autoLogout();
notifyListeners();
final prefs = await SharedPreferences.getInstance();
final userData = json.encode({
'token': _token,
'userId': userId,
'expiryDate': _expiryDate.toIso8601String(),
});
prefs.setString('userData', userData);
} catch (error) {
throw error;
}
}
//Auto Login Function
Future<bool> tryAutoLogin() async {
final prefs = await SharedPreferences.getInstance();
if (!prefs.containsKey('userData')) {
return false;
}
final extractedUserData =
json.decode(prefs.getString('userData')) as Map<String, Object>;
final expiryDate = DateTime.parse(extractedUserData['expiryDate']);
if (expiryDate.isBefore(DateTime.now())) {
return false;
}
_token = extractedUserData['token'];
userId = extractedUserData['userId'];
_expiryDate = expiryDate;
notifyListeners();
_autoLogout();
return true;
}
//SignUp function
Future<void> signUp(String email, String password) async {
return _authenticate(email, password, 'signUp');
}
//Login Function
Future<void> login(String email, String password) async {
return _authenticate(email, password, 'signInWithPassword');
}
//Logout Function
Future<void> logout() async {
_token = null;
userId = null;
_expiryDate = null;
if (_authTimer != null) {
_authTimer.cancel();
_authTimer = null;
}
notifyListeners();
final prefs = await SharedPreferences.getInstance();
prefs.clear();
}
//Auto Logout function
void _autoLogout() {
if (_authTimer != null) {
_authTimer.cancel();
}
final timeToExpiry = _expiryDate.difference(DateTime.now()).inSeconds;
_authTimer = Timer(Duration(seconds: timeToExpiry), logout);
}
//PHP related functions
}
Thank you in advance.
I agree with #lyio, you need to modify the function to pass the context, however after passing context, you cannot call it from initState as stated in documentation of initState
BuildContext.dependOnInheritedWidgetOfExactType from this method. However, didChangeDependencies will be called immediately following this method, and BuildContext.dependOnInheritedWidgetOfExactType can be used there.
Getting provider with Provider.of(context) under the hood is using the inherited widget, so cannot be called using context from initState
So implement instead of initState use didChangeDependencies to call your fetchStudentsInfo(context) method
Wouldn't the easiest solution be to pass the context into fetchStudentInfo?
You would change fetchStudentInfo() to fetchStudentInfo(BuildContext context). And then, when you call the method you pass in the required context. That way, you have the appropriate context available.
If you are not using the `fetchStudentInfo()` outside of the state class, then just move that method into the state class and the issue will be resolved.
Since Any state class has a context getter defined by default./
I just realized how improper this answer was.
Update:
According to the answer by #dlohani, didChangeDependencies should be used in stead of initState.
So what you can do is following:
Pass BuildContext as parameter in the fetchStudentInfo method
Override didChangeDependencies in state class & call fetchStudentInfo from here instead of initState

Show dialog using Scoped model

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();
}
}