Provider dont update a data in Flutter - flutter

I'm create a project on Flutter. And I'm using a provider to change screens in my app.
Here is my main.dart file:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:school_app/services/auth_service.dart';
import 'package:school_app/wrapper.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => AuthService().auth,
child: MaterialApp(
home: Wrapper(),
),
);
}
}
Also this is my wrapper.dart file where the screens choose:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:school_app/screens/authenticate/auth.dart';
import 'package:school_app/models/user.dart';
import 'package:school_app/screens/school/home.dart';
import 'package:school_app/services/auth_service.dart';
class Wrapper extends StatelessWidget {
#override
Widget build(BuildContext context) {
final user = Provider.of<AuthProvider>(context);
print(user.auth);
if(!user.auth) return Auth();
return Home();
}
}
And it is my AuthProvider class:
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
class AuthService {
/* AuthUser _user(User user) {
return user != null ? AuthUser(uid: user.uid) : null;
}*/
AuthProvider auth = new AuthProvider();
//sign in
Future signIn(String username, String password) async {
try {
SharedPreferences prefs = await SharedPreferences.getInstance();
var dio = Dio();
Response user = await dio.post('url', data: {
'username': username,
'password': password
});
if(user.data['success'] == false) return user.data['msg'];
await prefs.setString('token', user.data['token']);
auth.setAuth(true);
print("SUCCESS");
} catch(e) {
print('Error ' + e.toString());
}
}
}
class AuthProvider with ChangeNotifier {
bool _auth;
AuthProvider() {
_auth = false;
}
bool get auth => _auth;
void setAuth(bool auth) {
_auth = auth;
notifyListeners();
}
}
And when I call a function in AuthProvider class setAuth, nothing changed. Can you help me and find my mistake?
EDIT
I'm making all changes that you writes but it is not working. Here is my main.dart:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:school_app/services/auth_service.dart';
import 'package:school_app/wrapper.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => AuthProvider(),
child: MaterialApp(
home: Wrapper(),
),
);
}
}
Also wrapper.dart:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:school_app/screens/authenticate/auth.dart';
import 'package:school_app/screens/school/home.dart';
import 'package:school_app/services/auth_service.dart';
class Wrapper extends StatefulWidget {
#override
_WrapperState createState() => _WrapperState();
}
class _WrapperState extends State<Wrapper> {
#override
void initState() {
// TODO: implement initState
super.initState();
AuthService().auth;
}
#override
Widget build(BuildContext context) {
return Consumer<AuthProvider>(builder: (context, authProvider, child) {
print(authProvider.auth);
if (!authProvider.auth) {
return Auth();
} else {
return Home();
}
});
}
}
And AuthService and AuthProvider classes:
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
class AuthService {
/* AuthUser _user(User user) {
return user != null ? AuthUser(uid: user.uid) : null;
}*/
AuthProvider auth = new AuthProvider();
//sign in
Future signIn(String username, String password) async {
try {
SharedPreferences prefs = await SharedPreferences.getInstance();
var dio = Dio();
Response user = await dio.post('url', data: {
'username': username,
'password': password
});
if(user.data['success'] == false) return user.data['msg'];
await prefs.setString('token', user.data['token']);
auth.setAuth(true);
print("SUCCESS");
} catch(e) {
print('Error ' + e.toString());
}
}
}
class AuthProvider with ChangeNotifier {
bool _auth;
AuthProvider() {
_auth = false;
}
bool get auth => _auth;
void setAuth(bool auth) {
_auth = auth;
notifyListeners();
}
}
Notice, that here two classes and in AuthService I'm calling function .setAuth(true).

In your current implementation of Wrapper, you are rendering the widget once and not listening to whether the values changed. You could use Consumer as suggested above. You could also choose to watch the value for changes - like this:
class Wrapper extends StatelessWidget {
#override
Widget build(BuildContext context) {
final user = context.watch<AuthProvider>();
print(user.auth);
if(!user.auth) return Auth();
return Home();
}
}
When you use a watch or Consumer pattern, the widget will be rendered when the values of the underlying store (which is AuthProvider here) gets changed.

The only missing part here is that you never Consume the AuthProvider to listen to the notifyListeners() trigger.
The correct implementation looks like the following (I didn't try it, you may have to correct some typo errors, but you'll get the idea !)
class Wrapper extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Consumer<AuthProvider>(
builder: (context, authProvider, child) {
if (!authProvider.auth) {
return Auth();
} else {
return Home();
}
}
);
}
}
EDIT
I didn't notice you weren't injecting the right Class in your ChangeNotifierProvider. You'll also have to update your widget MyApp
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => AuthProvider(),
child: MaterialApp(
home: Wrapper(),
),
);
}
And in this case you probably should transform your Wrapper widget to a Stateful widget, and in the initState method you should call AuthService().auth.
I strongly recommend you to read the official documentation of Provider, looks like things aren't crystal clear yet in your mind
EDIT 2
You're still missing the point of the Provider library.
The goal of this lib is to provide an instance of a class to your widget tree so you don't have to re-create an instance in each widget.
Here, in AuthService class you're re-creating a AuthProvider with AuthProvider auth = new AuthProvider(); instead of referring to the existing instance created in the parent Widget.
To refer to a previously created instance, you should use Provider.of<AuthProvider>(context); in the AuthService class, or, even better, pass the instance of AuthProvider as a parameter in the signIn method.

Related

How to make me Signout in Flutter

I am creating a Login , Logout Page in flutter when i use Stream and Provider . I get Some Errors.
Help me out of this
in the main.dart when i use the StreamProvider it says the intial data cannot be null, according to my Tutorial there is no intialdata and in auth.dart when i use the authStatchanges().map it gives me 'The argument type 'Userid Function(User)' can't be assigned to the parameter type 'Userid Function(User?)'
Error Places - auth change user stream & and StreamProvider in main.dart
Thanks
Auth.dart
import 'package:firebase_auth/firebase_auth.dart';
import 'package:wasthu/Services/user.dart';
class AuthService {
final FirebaseAuth _auth = FirebaseAuth.instance;
//create userobject based on firebase user
Userid? _userFromFirebaseUser(User user) {
return user != null ? Userid(uid: user.uid) : null;
}
// auth change user stream
Stream<Userid> get user {
return _auth
.authStateChanges()
.map((User user) => _userFromFirebaseUser(user));
}
//sign in anon
Future signInAnon() async {
try {
UserCredential result = await _auth.signInAnonymously();
User? user = result.user;
return _userFromFirebaseUser(user!);
} catch (e) {
print(e.toString());
return null;
}
}
//signout
Future signOut() async {
try {
return await _auth.signOut();
} catch (e) {
print(e.toString());
return null;
}
}
}
main.dart
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:wasthu/Screens/Home/wrapper.dart';
import 'package:wasthu/Services/auth.dart';
import 'package:wasthu/Services/user.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MaterialApp(
home: MyApp(),
debugShowCheckedModeBanner: false,
));
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return StreamProvider<Userid>.value(
value: AuthService().user,
initialData: null,
child: MaterialApp(
home: Wrapper(),
),
);
}
}
user.dart
class Userid {
final String uid;
Userid({required this.uid});
}

State doesn't change in Flutter app using BLoC

I'm building an app which fetches hotel names from an API. I'm using the BLoC library. I managed to create whole service which downloads the data, but the result doesn't show in my terminal.
My BLoC works, it downloads the data. I saw it in Dart DevTools, but the state doesn't change and it does not show up.
Here's my code:
hotel_bloc.dart
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:hotels/controllers/hotel/hotel_controller.dart';
import 'package:hotels/models/hotel/hotel_model.dart';
import 'package:meta/meta.dart';
part 'hotel_event.dart';
part 'hotel_state.dart';
class HotelBloc extends Bloc<HotelEvent, HotelState> {
HotelBloc() : super(HotelLoading());
final HotelController hotelController = HotelController();
#override
Stream<HotelState> mapEventToState(
HotelEvent event,
) async* {
if (event is FetchEvent) {
yield HotelLoading();
try {
final Hotels hotels = await hotelController.parseHotels();
yield HotelFinal(hotels);
} catch (error) {
HotelError(error);
}
}
}
}
hotel_state.dart
part of 'hotel_bloc.dart';
#immutable
abstract class HotelState {
HotelState();
}
class HotelFinal extends HotelState {
final Hotels hotels;
HotelFinal(this.hotels);
Hotels getHotel() {
return hotels;
}
}
class HotelLoading extends HotelState {
HotelLoading();
}
class HotelError extends HotelState {
final String error;
HotelError(this.error);
}
hotel_event.dart
part of 'hotel_bloc.dart';
#immutable
abstract class HotelEvent {
HotelEvent();
}
class FetchEvent extends HotelEvent {
FetchEvent();
}
hotel_service.dart
import 'package:http/http.dart' as http;
abstract class DownloadService {
Future<http.Response> fetchHotels();
}
class HotelService extends DownloadService {
#override
Future<http.Response> fetchHotels() {
final Uri uri = Uri.https('services.lastminute.com', 'mobile/stubs/hotels');
return http.get(uri);
}
}
hotel_controller.dart
import 'package:hotels/models/hotel/hotel_model.dart';
import 'package:hotels/services/hotel/hotel_service.dart';
class HotelController {
final HotelService hotelService = HotelService();
Future<Hotels> parseHotels() async {
final response = await hotelService.fetchHotels();
final hotels = hotelsFromJson(response.body);
return hotels;
}
}
And finally the HomeScreen
home_screen.dart
import 'package:flutter/material.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hotels/blocs/hotel/hotel_bloc.dart';
class HomeScreen extends StatefulWidget {
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
HotelBloc hotelBloc;
#override
void initState() {
hotelBloc = HotelBloc()..add(FetchEvent());
super.initState();
}
#override
void dispose() {
hotelBloc.close();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('title').tr(),
),
body: BlocConsumer<HotelBloc, HotelState>(
listener: (context, state) {
if (state is HotelError) {
print(state.error);
}
},
builder: (context, state) {
if (state is HotelLoading) {
print('It\'s loading!');
}
if (state is HotelFinal) {
print(state.hotels.toString());
}
return Text('Default text');
},
),
);
}
}
The result is this:
The problem is the you haven't provided the BlocConsumer with your hotelBloc. You want to either have BlocProvider as a parent or use the cubit parameter on BlocConsumer.
BlocConsumer<HotelBloc, HotelState>(
cubit: hotelBloc,
listener:...
builder:...
)

Flutter: could not find the Provider above FutureBuilder

I'm trying to navigate to a screen where depending on 3 factors:
If the app has run before or not
If the user is null or not
If the email is verified or not.
import 'package:client/providers/auth_provider.dart';
import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../screens/home_screen.dart';
import '../screens/login_screen.dart';
class Wrapper extends StatefulWidget {
#override
_WrapperState createState() => _WrapperState();
}
class _WrapperState extends State<Wrapper> {
FirebaseAuth auth = FirebaseAuth.instance;
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext ctx) {
return FutureBuilder(
future: hasAlreadyStarted(),
builder: (ctx, snapshot) {
return Provider.of<User>(ctx) != null &&
snapshot.data == true &&
AuthProvider().isEmailVerified
? HomeScreen()
: LoginScreen();
});
}
Future<bool> hasAlreadyStarted() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
try {
if (prefs.getBool("hasRun") == null) {
await AuthProvider().logout();
setState(() {
prefs.setBool("hasRun", true);
});
return true;
} else {
return true;
}
} catch (error) {
setState(() {
prefs.setBool("hasRun", null);
});
return false;
}
}
}
The screen works fine on it's own, but when i Navigate from a different screen it gives me the error.
Could not find the correct Provider<User> above this FutureBuilder<bool> Widget
Can you explain the error and give me a solution. Thanks :D
I just needed to wrap the entire MaterialApp with the StreamProvider

initialRoute string is changed, but I end up at the same page regardless the initialRoute string

When using shared_preferences on flutter in main.dart in order to change the initialRoute depending on if user have seen the first page or if user is logged in I am getting the boolean which is created throughout the app and added to shared_preferences, every time I start app, I get the initialRoute string correct when debugging, but I still end up getting on the first page, regardless the conditions.
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'dart:developer';
import './pages/registration.dart';
import './pages/login_page.dart';
import './pages/confirmation.dart';
import './pages/lang_page.dart';
import './pages/main_page.dart';
import './pages/user_data.dart';
import './provider/provider.dart';
void main() => runApp(CallInfoApp());
class CallInfoApp extends StatefulWidget {
#override
_CallInfoAppState createState() => _CallInfoAppState();
}
class _CallInfoAppState extends State<CallInfoApp> {
SharedPreferences prefs;
void getSPInstance() async {
prefs = await SharedPreferences.getInstance();
}
dynamic langChosen;
dynamic isLoggedIn;
String initialRoute;
void dataGetter() async {
await getSPInstance();
setState(() {
langChosen = prefs.getBool('langChosen');
// print(langChosen);
isLoggedIn = prefs.getBool('isLoggedIn');
});
}
void getRoute() async {
await dataGetter();
debugger();
if (langChosen == true && isLoggedIn != true) {
setState(() {
initialRoute = '/login_page';
});
} else if (isLoggedIn == true) {
initialRoute = '/main_page';
} else {
setState(() {
initialRoute = '/';
});
}
}
#override
void initState() {
super.initState();
debugger();
getRoute();
}
#override
Widget build(BuildContext context) {
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
]);
return ChangeNotifierProvider<AppData>(
create: (context) => AppData(),
child: MaterialApp(
title: 'Call-INFO',
theme: ThemeData(
primarySwatch: Colors.blue,
),
initialRoute: initialRoute,
routes: {
'/': (context) => LanguagePage(),
'/registration_page': (context) => RegistrationPage(),
'/login_page': (context) => LoginPage(),
'/confirmation_page': (context) => ConfirmationPage(),
'/user_data_page': (context) => UserDataPage(),
'/main_page': (context) => MainPage(),
},
),
);
}
}
Since SharedPreference.getInstance() is an async function it will need some time until the instance is available. If you want to use it for initial route you have to make your main function async and preload it there before your MaterialApp is build.
SharedPreference prefs; //make global variable, not best practice
void main() async {
prefs = await SharedPreference.getInstance();
runApp(CallInfoApp());
}
And remove getSPInstance() from dataGetter
Also keep in midn that prefs.getBool('langChosen') will return null and not false if no entry is made into shared preference so use
langChosen = prefs.getBool('langChosen')??false;
isLoggedIn = prefs.getBool('isLoggedIn')??false;
While this solution will work it's not really good practice. I would recommend to have the initialRoute fixed to a splash screen and handle forwarding to the right page from there. A simple splash screen could look like that:
class SplashScreen extends StatefulWidget {
#override
_SplashScreenState createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> {
#override
Widget build(BuildContext context) {
return Scaffold(body: Center(child: CircularProgressIndicator()));
}
#override
void initState() {
initSplash();
super.initState();
}
Future<void> initSplash() async {
final prefs = await SharedPreferences.getInstance();
final langChosen = prefs.getBool("lang_chosen") ?? false;
final isLoggedIn = prefs.getBool("logged_in") ?? false;
if (langChosen == true && isLoggedIn != true) {
Navigator.of(context).pushReplacementNamed('/login_page');
} else if (isLoggedIn == true) {
Navigator.of(context).pushReplacementNamed('/main_page');
} else {
Navigator.of(context).pushReplacementNamed('/');
}
}
}
Use initState to derive the data your logic is based on (i.e. fetching shared pref info). And use await keyword so that program will wait until the data is fetched from SharedPrefs. Adding the following code to class _CallInfoAppState should help
#override
void initState() {
super.initState();
dataGetter();
}
void dataGetter() async {
await getSPInstance();
setState(() {
langChosen = prefs.getBool('langChosen');
isLoggedIn = prefs.getBool('isLoggedIn');
});
}

API call returns the json string twice

I'm trying to return a json string in Flutter. I'm using the print function to display the output in the console. However my code is returning the String twice. Here is my code:
import 'package:flutter/material.dart';
import 'dart:convert';
import 'package:http/http.dart' as http;
void main() => runApp(new HomePage());
class HomePage extends StatelessWidget {
Future<http.Response> fetchPost() async{
return await http.get('https://api.npoint.io/8c7aafe809d73af5f2b9');
}
void Data() async {
var jsonString = await fetchPost();
print(jsonString.body);
}
#override
Widget build(BuildContext context) {
Data();
return new MaterialApp(
home: new Center(
child: new Text('Data'),
),
);
}
}
The build method is called twice causing the whole widget to be called again. Consider converting StatelessWidget to StatefulWidget and add your http call method in
#overridde
initState() {
your code
}
I had the same issue and solved using try catch
Future<http.Response> fetchPost() async{
try{
return await http.get('https://api.npoint.io/8c7aafe809d73af5f2b9');
}
catch (e) {
print(e);
}
}