Error: Could not find the correct Provider<WgService> above this landing widget - flutter

class Landing extends StatelessWidget {
const Landing({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
AuthService auth = Provider.of<AuthService>(context);
return StreamBuilder<FirebaseUser>(
stream: auth.onAuthStateChanged,
builder: (contexta, snapshot) {
FirebaseUser user = snapshot.data;
WgService wg = Provider.of<WgService>(context);
if (user == null)
return LoginView();
else
return StreamBuilder<WGDocument>(
stream: wg.streamWG('demowg'),
builder: (contextWG, snapshotWG) {
WGDocument currentWG = snapshotWG.data;
if (currentWG != null)
return SignedInView();
else
return JoinWGScreen();
});
});
}
}
I readed multiple issues with the same error but cant get it fixed by myself. I tried every other context and I do not understand why the error occurs. No IDE errors given.

You need to put a Provider widget on top of your widget. Then you build your widgets as an ancestor of that provider widget. Any descendant can reach the data class of that provider.
Provider<AuthService>(
create: (_) => AuthService(),
child: /* Any widgets below can reach AuthService */
)

Related

problem when get user data from firebase firestore depend on data from firebase auth

i have this problem, when try to get user from firebase auth using streambuilder, and then get the user data from firestore depending on the user id, always this:
userDoc.data()
return a null?
this is the code :
StreamBuilder<User?>(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (context, authSnapshot) {
// If the snapshot has user data, then they're already signed in. So Navigating to the Dashboard.
if (authSnapshot.hasData && authSnapshot.data != null) {
//return const TeacherDashboard();
return StreamBuilder<DocumentSnapshot>(
stream: FirebaseFirestore.instance
.collection("users")
.doc(authSnapshot.data?.uid)
.snapshots(),
builder: (context,
AsyncSnapshot<DocumentSnapshot> userSnapshot) {
if (userSnapshot.hasData && userSnapshot.data != null) {
final userDoc = userSnapshot.data;
print(userDoc!.get('isTeacher'));
final user = (userDoc != null
? userDoc.data()
: {"isTeacher": 0}) as Map<String, dynamic>;
if (user['isTeacher'] == 1) {
return const TeacherDashboard();
} else {
return const StudentsScreen();
}
} else {
return const Center(
child: CircularProgressIndicator(),
);
}
});
I assume You want to know the user is a teacher or a student. if teacher, go to teacher page, if student go to student page. and you are using a value to detect the user is a teacher or student. the value is 1.
so, if user value is == 1 go to teacher page. or go to student page.
if you want this function only you do not need to create a streambuilder here. you just need to get the user value. That you can achieve like this:
// Here I created one HomePage to decide which Screen to visit.
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
#override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
int? _value;
#override
void initState() {
super.initState();
getUserValue();
}
void getUserValue() async {
DocumentSnapshot snap = await FirebaseFirestore.instance
.collection('users')
.doc(FirebaseAuth.instance.currentUser!.uid)
.get();
setState(() {
_value = (snap.data() as Map<String, dynamic>)['isTeacher'];
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: _value == null
? const Center(
child: CircularProgressIndicator(),
)
: (_value == 1)
? const TeacherDashboard()
: const StudentsScreen(),
);
}
}
sidenote: I think you getting the error because You using Stateless widget. It's very important to use a Stateful widget and initially keep the value null. and if value is null show something like CircularProgressIndicator(). once value is available go to different Screen. in Stateless widget once the widget is built already it will get the value but will not rebuilt anything. so null value will decide your widget what gives you the error. and You must setState() Once you get the value.
Hope this will solve your problem.

"await" in Widget build FLUTTER

I've been stuck for several hours with a problem on flutter. If you can help me that would be really nice.
I need to put "await" in my Widget build(BuildContext context){} but it's impossible to put "async".
How to do ?
When i test void _myAsyncMethod()async{} :
To Fix your issue you can put async in the body of method like this
Before=> Widget build(BuildContext context) {
After=> Widget build(BuildContext context) async{
Although this will not solve your problem as flutter wiill warn you as this is not the proper way to do it.
It's not a good practice to call await inside flutter's build method Because
Generally an apps need to run a 60 frames per second on an average hence flutter's build method we'll be called over and over to re-render the ui.
Another reason is that, doing calling await function() in build method will block your UI.
Solution
use FutureBuilder
call await auth.currentUser() in initState method
Another way to solve this is to use FutureBuilder
sample Code for 1
FutureBuilder(
builder: (BuildContext ctx, AsyncSnapshot<userModel> snapshot) {
if(ConnectionState.done == snapshot.connectionState) {
return Text(snapshot.data.userId);
} else {
return CircularProgressIndicator();
}
},
future: auth.currentUser(),
);
sample Code for 2(stateful widget)
late UserModel;
void initState() {
UserModel user = await auth.currentUser();
}
this is very basic code but it's enough for you to get started.
Note: I've assumed userModel mentioned above is response type of auth.currentUser() you can change it accordingly.
What you want to do is not optimal but you can create a method and put your await variable in there:
late final FirebaseUser _user;
void _myAsyncMethod()async{
_user = await auth.currentUser;
}
#override
Widget build(BuildContext context) {
_myAsyncMethod();
return Scaffold(appBar: AppBar(), body: Container());
}
If your are using stateful widget you can instantiate firebase auth in initstate() method.
class testFirless extends StatefulWidget {
var currentuseid = "";
testFirless({Key? key}) : super(key: key);
#override
_testFirlessState createState() => _testFirlessState();
}
class _testFirlessState extends State<testFirless> {
#override
Widget build(BuildContext context) {
return Container();
}
// ------------------------------------>heree
#override
Future<void> initState() async {
FirebaseAuth auth = FirebaseAuth.instance;
var user = await auth.currentUser;
if (user == null) {
widget.currentuseid = user!.uid;
} else {
print('User is signed in!');
}
}
}
FutureBuilder
class fbuilder extends StatelessWidget {
const fbauth({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
FirebaseAuth auth = FirebaseAuth.instance;
// --------------->
return Container(child: FutureBuilder(
builder: (BuildContext ctx, AsyncSnapshot<User> snapshot) {
if (ConnectionState.done == snapshot.connectionState) {
return Text(snapshot.data.userId.toString());
} else {
return CircularProgressIndicator();
}
},
future: auth.currentUser(),
));
}
}
in stateless or stateful widget
String currentuseid="";
class fbauth extends StatelessWidget {
const fbauth({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
FirebaseAuth auth = FirebaseAuth.instance;
// ------------------------>
auth.currentUser().then((user) {
if (user == null) {
currentuseid = user!.uid;
} else {
print('User is signed in!');
}
// other logic after the user retrieval
});
return Container();
}
}
Nb: Instead of instantiating firebase auth in every widget .you must instantiate in `void main` method

Flutter Http get coming Null

I started to learn work with APIs. I tried an api without authentication reqres.in and it worked for me . But now im trying to get datas from a api which is work with key, and this datas coming null. When i used my key like this on web browsers https://api.rawg.io/api/games?key=MY_API_KEY&page=1 its working ( i can see JSON data). But when i use this api key for my working code datas coming null and i couldn't understand why.
Here is my code
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
Future<Welcome> apiCall() async {
final response = await http.get(Uri.parse(
'https://api.rawg.io/api/games?key=84e37baf...(mykey)&page=1'));
return Welcome.fromJson(jsonDecode(response.body));
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: FutureBuilder<Welcome>(
future: apiCall(),
builder: (context, snapshot) {
var growableList = [];
List<Result> data = snapshot.data!.results;
growableList.add(data[0].name);
return Text('${growableList}');
},
),
),
);
}
}
FutureBuilders work by calling the async function and then immediately calling build. This is why you're seeing null. They will rebuild again once the future returns.
Even though you've marked apiCall as async the builder will not wait for it to finish. What you need to do is check snapshot.hasData which will only be true once the future has completed. When snapshot.hasData is false, you need to return a different widget.
It might also be worth using snapshot.hasError which is a bool representing if an error occurred while completing the async function.
Try this code out
import 'package:flutter/material.dart';
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
Future<Welcome> apiCall() async {
final response = await http.get(Uri.parse(
'https://api.rawg.io/api/games?key=84e37baf...(mykey)&page=1'));
return Welcome.fromJson(jsonDecode(response.body));
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: FutureBuilder<Welcome>(
future: apiCall(),
builder: (context, snapshot) {
if(snapshot.hasError){
print(snapshot.error);
}
if(snapshot.hasData){
var growableList = [];
List<Result> data = snapshot.data!.results;
growableList.add(data[0].name);
return Text('${growableList}');
} else {
return CircularProgressIndicator();
}
},
),
),
);
}
}
I fixed the problem. Problem was in dataclass, there was some classes which has same names and variable problems like int or double, For exmaple When i tried to generate my code with looking example response i used int except double. But response in real getting double variables. Mistakes like this doesn't let me print it.

Flutter: Stateful Widget does not update

Imagine two Widgets: Main that manages a tabbar and therefore holds several Widgets - and Dashboard.
On Main Constructor I create a first Instance of Dashboard and the other tabbar Widgets with some dummy data (they are getting fetched in the meanwhile in initState). I build these with Futurebuilder. Once the data arrived I want to create a new Instance of Dashboard, but it won't change.
class _MainState extends State<HomePage> {
var _tabs = <Widget>[];
Future<dynamic> futureData;
_MainState() {
_tabs.add(Dashboard(null));
}
#override
void initState() {
super.initState();
futureData = _getData();
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: futureData,
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.data != null) {
tabs[0] = Dashboard(snapshot.data);
} else {
return CircularProgressIndicator();
}
});
}
}
class DashboardScreen extends StatefulWidget {
final data;
DashboardScreen(this.data,
{Key key})
: super(key: key) {
print('Dashboard Constructor: ' + data.toString());
}
#override
_DashboardScreenState createState() => _DashboardScreenState(data);
}
class _DashboardScreenState extends State<DashboardScreen> {
var data;
_DashboardScreenState(this.data);
#override
void initState() {
super.initState();
print('InitState: ' + data.toString());
}
#override
void didUpdateWidget(Widget oldWidget) {
super.didUpdateWidget(oldWidget);
print('didUpdateWidget');
}
#override
void didChangeDependencies() {
super.didChangeDependencies();
print('didChangeDependencies' + data.toString());
}
#override
Widget build(BuildContext context) {
return Text(data.toString());
}
}
When I print on several available methods it comes clear that the DasboardScreenState is not recreated. Only the DashboardScreen Constructor is called again when the data arrived, but not it's state...
flutter: MainConstructor: null
flutter: Dashboard Constructor: null
flutter: InitState: null
flutter: didChangeDependencies: null
flutter: Dashboard Constructor: MachineStatus.Manual <- Here the data arrived in futureBuilder
How can I force the State to recreate? I tried to use the key parameter with UniqueKey(), but that didn't worked. Also inherrited widget seems not to be the solution either, despite the fact that i don't know how to use it in my use case, because the child is only available in the ..ScreenState but not the updated data..
I could imagine to inform dashboardScreenState by using Stream: listen to messages and then call setState() - I think, but that's only a workaround.
Can anyone help me please :)?
I know I have had issues with the if statement before, try:
return FutureBuilder(
future: futureData,
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) { //use hasData
DataType data = snapshot.data; //Declare Values first
tabs[0] = Dashboard(data);
} else {
return CircularProgressIndicator();
}
});

Declarative auth routing with Firebase

Rather than pushing the user around with Navigator.push when they sign in or out, I've been using a stream to listen for sign in and sign out events.
StreamProvider<FirebaseUser>.value(
value: FirebaseAuth.instance.onAuthStateChanged,
)
It works great for the home route as it handles logging in users immediately if they're still authed.
Consumer<FirebaseUser>(
builder: (_, user, __) {
final isLoggedIn = user != null;
return MaterialApp(
home: isLoggedIn ? HomePage() : AuthPage(),
// ...
);
},
);
However, that's just for the home route. For example, if the user then navigates to a settings page where they click a button to sign out, there's no programmatic logging out and kicking to the auth screen again. I either have to say Navigator.of(context).pushNamedAndRemoveUntil('/auth', (_) => false) or get an error about user being null.
This makes sense. I'm just looking for possibly another way that when they do get logged out I don't have to do any stack management myself.
I got close by adding the builder property to the MaterialApp
builder: (_, widget) {
return isLoggedIn ? widget : AuthPage();
},
This successfully moved me to the auth page after I was unauthenticated but as it turns out, widget is actually the Navigator. And that means when I went back to AuthPage I couldn't call anything that relied on a parent Navigator.
What about this,you wrap all your screens that depend on this stream with this widget which hides from you the logic of listening to the stream and updating accordingly(you should provide the stream as you did in your question):
class AuthDependentWidget extends StatelessWidget {
final Widget childWidget;
const AuthDependentWidget({Key key, #required this.childWidget})
: super(key: key);
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: FirebaseAuth.instance.onAuthStateChanged,
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {//you handle other cases...
if (snapshot.currentUser() != null) return childWidget();
} else {
return AuthScreen();
}
},
);
}
}
And then you can use it when pushing from other pages as follows:
Navigator.of(context).pushReplacement(MaterialPageRoute(
builder: (ctx) => AuthDependentWidget(
childWidget: SettingsScreen(),//or any other screen that should listen to the stream
)));
I found a way to accomplish this (LoVe's great answer is still completely valid) in case anyone else steps on this issue:
You'll need to take advantage of nested navigators. The Root will be the inner navigator and the outer navigator is created by MaterialApp:
return MaterialApp(
home: isLoggedIn ? Root() : AuthPage(),
routes: {
Root.routeName: (_) => Root(),
AuthPage.routeName: (_) => AuthPage(),
},
);
Your Root will hold the navigation for an authed user
class Root extends StatefulWidget {
static const String routeName = '/root';
#override
_RootState createState() => _RootState();
}
class _RootState extends State<Root> {
final _appNavigatorKey = GlobalKey<NavigatorState>();
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
final canPop = _appNavigatorKey.currentState.canPop();
if (canPop) {
await _appNavigatorKey.currentState.maybePop();
}
return !canPop;
},
child: Navigator(
initialRoute: HomePage.routeName,
onGenerateRoute: (RouteSettings routeSettings) {
return MaterialPageRoute(builder: (_) {
switch (routeSettings.name) {
case HomePage.routeName:
return HomePage();
case AboutPage.routeName:
return AboutPage();
case TermsOfUsePage.routeName:
return TermsOfUsePage();
case SettingsPage.routeName:
return SettingsPage();
case EditorPage.routeName:
return EditorPage();
default:
throw 'Unknown route ${routeSettings.name}';
}
});
},
),
);
}
}
Now you can unauthenticate (FirebaseAuth.instance.signout()) inside of the settings page (or any other page) and immediately get kicked out to the auth page without calling a Navigator method.