I'm trying to make a flutter mobile app with my own back end using Django rest framework, and JWT
My flow is, after signing in i will store access token in local storage, and create a stream to update the widget (like FirebaseAuth.instance.authStateChanges()). It's working fine but after i switching screen, my stream has NO DATA, and it show login screen
Below is my creation of that stream (token_services.dart), while localStorage is from localstorage package
StreamController<String> accessTokenStreamController =
StreamController<String>.broadcast();
Stream<String> get accessTokenStream => accessTokenStreamController.stream;
void setAccessToken(String accessToken) {
localStorage.setItem('access_token', accessToken);
accessTokenStreamController.add(accessToken);
}
String getAccessToken() {
return localStorage.getItem('access_token');
}
Here is my screen (account_screen.dart):
class AccountScreen extends StatefulWidget {
#override
_AccountScreenState createState() => _AccountScreenState();
}
class _AccountScreenState extends State<AccountScreen> {
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: accessTokenStream,
builder: (context, snapshot) {
if (snapshot.hasData && snapshot.data != null)
return AuthorizedScreen();
else
return UnauthorizedScreen();
});
}
}
And the root screen:
class Wrapper extends StatefulWidget {
#override
_WrapperState createState() => _WrapperState();
}
class _WrapperState extends State<Wrapper> {
int status = 0;
List<Map> screens = [
{'name': 'Home', 'widget': HomeScreen(), 'icon': Icon(Icons.home)},
{
'name': 'Account',
'widget': AccountScreen(),
'icon': Icon(Icons.account_circle)
},
];
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(child: screens[status]['widget']),
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
currentIndex: status,
items: screens
.map((scr) =>
BottomNavigationBarItem(label: scr['name'], icon: scr['icon']))
.toList(),
onTap: (value) {
setState(() {
status = value;
});
},
),
);
}
}
Note that if i provide initialData to StreamBuilder, it's completely fine
Related
I have a small flutter application that uses Firebase Auth to login and then uses bindStream to query a list of documents from Firestore. It works from a fresh start/hot restart, but as soon as I logout I get a firebase/firestore permission error and subsequent login's don't refresh the stream. I thought that a GetxController disposes streams created via bindStream when the view that uses the controller is disposed. In this case, when I logout I pop off all routes via Get.offAll, but it appears the stream is still active and that's when the permissions error happens. But I'm not actually sure what is happening.
main.dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
Get.put(LoginController());
Get.put(AuthController(), permanent: true);
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return GetMaterialApp(
debugShowCheckedModeBanner: false,
title: 'GetX Firebase Firestore',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: SplashScreen(),
);
}
}
auth_controller.dart
class AuthController extends GetxController {
final AuthService _authService = AuthService();
AuthService get authService => _authService;
final LoginController _loginController = Get.find<LoginController>();
LoginController get loginController => _loginController;
Rxn<User> _user = Rxn<User>();
User? get user => _user.value;
#override
void onReady() async {
// bind auth state to _firebaesUser, but also give an initial value
_user = Rxn<User>(_authService.currentUser);
_user.bindStream(_authService.authState);
//run every time auth state changes
ever<User?>(_user, handleAuthChanged);
super.onReady();
}
handleAuthChanged(User? user) {
print("handleAuthChanged - ${user?.uid}");
if (user == null) {
Get.offAll(() => LoginScreen());
} else {
Get.offAll(() => HomeScreen(), binding: HomeBinding());
}
}
}
user_controller.dart
class UserController extends GetxController {
final UserRepository _userRepository = UserRepository();
final repository = UserRepository();
final users = Rx<List<FirestoreUser>>([]);
late Rx<FirestoreUser> _firestoreUser;
FirestoreUser get firestoreUser => _firestoreUser.value;
#override
void onInit() {
super.onInit();
final user = FirebaseAuth.instance.currentUser;
if (user == null) return;
_firestoreUser = Rx<FirestoreUser>(FirestoreUser.fromAuth(user));
// get user data from firestore
_firestoreUser.bindStream(_userRepository.getUserById(user.uid));
// query user collection
getAllUsers();
}
void getAllUsers() {
users.bindStream(repository.getAllUsers());
}
}
home_screen.dart
class HomeScreen extends GetView<UserController> {
const HomeScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("All Sample Users"),
actions: [
IconButton(
onPressed: () => Get.to(ProfileScreen()),
icon: Icon(Icons.person),
),
],
),
body: Obx(
() => ListView.builder(
itemCount: controller.users.value.length,
itemBuilder: (context, index) {
final user = controller.users.value[index];
return ListTile(
leading: CircleAvatar(
backgroundImage: NetworkImage(user.photoURL),
),
title: Text(user.displayName),
);
},
),
),
);
}
}
home_binding.dart
class HomeBinding extends Bindings {
#override
void dependencies() {
Get.lazyPut<UserController>(() => UserController(), fenix: true);
}
}
I know I'm successfully fetching the data from the DB because I can print it out in fetchFavorites() but I'm not dealing with the List correctly in _getFavesState because my result is:
type List〈dynamic〉 is not a subtype of type 'FutureOr<List 〈String〉>'
So how do I actually create widgets from my data? Code:
Future<List<String>> fetchFavorites() async {
FirebaseFirestore firestore = FirebaseFirestore.instance;
final user = FirebaseAuth.instance.currentUser;
final userData = await firestore.collection('users').doc(user.uid).get();
var faves = userData.get("favorites");
return faves;
}
class GetFaves extends StatefulWidget {
#override
_GetFavesState createState() => _GetFavesState();
}
class _GetFavesState extends State<GetFaves> {
Widget build(BuildContext context) {
Future<List> favoritesList;
favoritesList = fetchFavorites();
return Column(children: [
favoritesList == null
? Text('No Favorites')
: FutureBuilder(
future: favoritesList,
builder: (context, snapshot) {
if (snapshot.hasData) {
print("has data");
return Container(
child: ListView.builder(
itemCount: snapshot.data.length,
scrollDirection: Axis.horizontal,
itemBuilder: (BuildContext context, int index) {
return Text('${snapshot.data[index].title}');
}));
} else if (snapshot.hasError) {
return Text('${snapshot.error.toString()}');
} else {
return CircularProgressIndicator();
}
}),
]);
}
}
EDIT:
I don't think it is a straight ahead list of strings. It is some kind of record that looks like a serialized JSON: {CoverUrl : https://...., Title: some title, cid: something} So the error makes sense. Not sure how that changes the solution.
A few remarks on your code:
You don't need a StatefulWidget since the data is managed by your FutureBuilder
favoritesList == null will always be false since it's a Future<List<String>>
In snapshot.data[index].title, snapshot.data[index] is a String, what is this title?
You can remove the Container, it just has a child and is therefore useless.
I think we can simplify a bit:
(I left out the Firestore part since you tell us it works fine. Though, are you sure userData.get("favorites") returns a String? If so, cast it to a String to match your Future<List<String>> signature)
Full source code
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
home: HomePage(),
),
);
}
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: GetFaves(),
);
}
}
class GetFaves extends StatefulWidget {
#override
_GetFavesState createState() => _GetFavesState();
}
class _GetFavesState extends State<GetFaves> {
Future<List<String>> _fetchFavorites() async {
return List.generate(10, (index) => 'Favorite $index');
}
#override
Widget build(BuildContext context) {
return FutureBuilder<List<String>>(
future: _fetchFavorites(),
builder: (context, snapshot) {
print(snapshot);
if (snapshot.hasData) {
print(snapshot.data);
return ListView(
scrollDirection: Axis.horizontal,
children: snapshot.data.map((favorite) => Text(favorite)).toList(),
);
} else if (snapshot.hasError) {
return Text(snapshot.error.toString());
} else {
return CircularProgressIndicator();
}
},
);
}
}
Update : Album from JSON Data
Your Albums are retrieved as JSON Data from Firestore: Here is an example:
[
{'cid': '593312', 'title': 'porttitor', 'coverUrl': 'https://source.unsplash.com/640x480'},
{'cid': '910654', 'title': 'auctor', 'coverUrl': 'https://source.unsplash.com/640x480'},
{'cid': '276961', 'title': 'nullam', 'coverUrl': 'https://source.unsplash.com/640x480'},
{'cid': '413021', 'title': 'rhoncus', 'coverUrl': 'https://source.unsplash.com/640x480'},
{'cid': '299898', 'title': 'posuere', 'coverUrl': 'https://source.unsplash.com/640x480'}
]
Such data in Dart is usually defined as a List<Map<String, dynamic>>.
In this solution, we will use the freezed package (depending on json_serializable package) to generate the Domain Class Album. The code of this class is generate in your_file.freezed.dart for the Immutable Domain Class and your_file.g.dart for the from/toJson functionality. To generate these two files, you will need to also install the Dev Dependency build_runner and run the following command at the root of your project:
flutter pub run build_runner watch --delete-conflicting-outputs
Once everything is setup, your _fetchFavorites() will become:
Future<List<Album>> fetchFavorites() async {
FirebaseFirestore firestore = FirebaseFirestore.instance;
final user = FirebaseAuth.instance.currentUser;
final userData = await firestore.collection('users').doc(user.uid).get();
var faves = userData.get("favorites");
return faves.map((jsonData) => Album.fromJson(jsonData)).toList();
}
Note: This snippet has not been tested
Full source code using dummy Data
Note: the faker package is used to generate random dummy data.
import 'package:faker/faker.dart';
import 'package:flutter/material.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part '66473551.future.freezed.dart';
part '66473551.future.g.dart';
void main() {
runApp(
MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
home: HomePage(),
),
);
}
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: FavoriteList(),
);
}
}
class FavoriteList extends StatelessWidget {
Future<List<Album>> _fetchFavorites() async {
return dummyData.map((jsonData) => Album.fromJson(jsonData)).toList();
}
#override
Widget build(BuildContext context) {
return FutureBuilder<List<Album>>(
future: _fetchFavorites(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return ListView(
children: snapshot.data
.map((favorite) => AlbumWidget(album: favorite))
.toList(),
);
} else if (snapshot.hasError) {
return Text(snapshot.error.toString());
} else {
return CircularProgressIndicator();
}
},
);
}
}
class AlbumWidget extends StatelessWidget {
final Album album;
const AlbumWidget({
Key key,
this.album,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return ListTile(
leading: Image.network(album.coverUrl),
title: Text(album.title),
subtitle: Text(album.cid),
);
}
}
#freezed
abstract class Album with _$Album {
const factory Album({String cid, String title, String coverUrl}) = _Album;
factory Album.fromJson(Map<String, dynamic> json) => _$AlbumFromJson(json);
}
final faker = new Faker();
final List<Map<String, dynamic>> dummyData = List.generate(
10,
(index) => {
'cid': faker.randomGenerator.integer(999999).toString(),
'title': faker.lorem.word(),
'coverUrl': faker.image.image(),
},
);
You do something like this
class GetFaves extends StatefulWidget {
#override
_GetFavesState createState() => _GetFavesState();
}
class _GetFavesState extends State<GetFaves> {
#override
void initState(){
super.initState();
fetchFavourites();
}
#override
Widget build(BuildContext context) {
List<Widget> widgetList = [SizedBox(height:50),CircularProgressIndicator()];
return Column(
children:widgetList,
)
}
void fetchFavorites() async {
widgetList = [SizedBox(height:50),CircularProgressIndicator()];
FirebaseFirestore firestore = FirebaseFirestore.instance;
final user = FirebaseAuth.instance.currentUser;
final userData = await firestore.collection('users').doc(user.uid).get();
var faves = userData.get("favorites");
widgetList = [];
//Build the list of Containers here with a for loop and store it in your variable widgetList. I could not understand what you were building so I left that part for you
setState((){});
}
}
What I am basically doing is showing a loading animation in the start and when the documents have been loaded using the function from initState and you have made the widgets, I rebuild the class with a list of new Widgets
What was happening was that I was fetching from the database a List of type dynamic, which in this case is some kind of deserialized JSON looking text. When I changed the function signature of my function fetchFavorites to this, the build method of my calling class started working:
Future<List<dynamic>> fetchFavorites() async {
...
}
The elements of the returned records are accessed as a 2-dimensional array, so:
record[index]["title"]
and so on.
I'm currently making an app with bottom navigator. And I have troubles with navigating from SecondScreen to the FirstScreen, programmatically, inside the SecondScreen file. But I have no idea how to do it. Because I can't have the access to the CustomNavigatorState part of the CustomNavigator class.
My main.dart file:
import 'package:flutter/material.dart';
import './screens/custom_navigator.dart';
void main() async {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'App Name',
home: Scaffold(
body: CustomNavigator(),
),
);
}
}
My custom_navigator.dart file, which includes CustomNavigator class and _CustomNavigatorState class:
import 'package:flutter/material.dart';
import './first_second.dart';
import './second_screen.dart';
import './third_screen.dart';
import '../widgets/tab_navigator.dart';
class CustomNavigator extends StatefulWidget {
#override
State<StatefulWidget> createState() => _CustomNavigatorState();
}
class _CustomNavigatorState extends State<CustomNavigator> {
String _currentScreen = FirstScreen.route;
List<String> _screenKeys = [
FirstScreen.route,
SecondScreen.route,
ThirdScreen.route,
];
Map<String, GlobalKey<NavigatorState>> _navigatorKeys = {
FirstScreen.route: GlobalKey<NavigatorState>(),
SecondScreen.route: GlobalKey<NavigatorState>(),
ThirdScreen.route: GlobalKey<NavigatorState>(),
};
int _selectedIndex = 0;
void changeTab(String tabItem, int index) {
_selectedTab(tabItem, index);
}
void _selectedTab(String tabItem, int index) {
if (tabItem == _currentScreen) {
_navigatorKeys[tabItem].currentState.popUntil((route) => route.isFirst);
} else {
setState(() {
_currentScreen = _screenKeys[index];
_selectedIndex = index;
});
}
}
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
final isFirstRouteInCurrentTab =
!await _navigatorKeys[_currentScreen].currentState.maybePop();
if (isFirstRouteInCurrentTab) {
if (_currentScreen != FirstScreen.route) {
_selectedTab(FirstScreen.route, 1);
return false;
}
}
return isFirstRouteInCurrentTab;
},
child: Scaffold(
resizeToAvoidBottomPadding: true,
body: Stack(
children: [
_buildOffstageNavigator(FirstScreen.route),
_buildOffstageNavigator(ScreenScreen.route),
_buildOffstageNavigator(ThirdScreen.route),
],
),
bottomNavigationBar: BottomNavigationBar(
onTap: (index) {
_selectedTab(_screenKeys[index], index);
},
currentIndex: _selectedIndex,
items: [
BottomNavigationBarItem(
label: 'First',
),
BottomNavigationBarItem(
label: 'Second',
),
BottomNavigationBarItem(
label: 'Third',
),
],
),
),
);
}
Widget _buildOffstageNavigator(String tabItem) {
return Offstage(
offstage: _currentScreen != tabItem,
child: TabNavigator(
navigatorKey: _navigatorKeys[tabItem],
tabItem: tabItem,
),
);
}
}
TabNavigator class, where the screens added.
import 'package:flutter/material.dart';
import '../screens/first_screen.dart';
import '../screens/second_screen.dart';
import '../screens/third_screen.dart';
class TabNavigator extends StatelessWidget {
final GlobalKey<NavigatorState> navigatorKey;
final String tabItem;
const TabNavigator({
Key key,
this.navigatorKey,
this.tabItem,
}) : super(key: key);
#override
Widget build(BuildContext context) {
Widget child;
if (tabItem == FirstScreen.route) {
child = FirstScreen();
} else if (tabItem == SecondScreen.route) {
child = SecondScreen();
} else if (tabItem == ThirdScreen.route) {
child = ThirdScreen();
}
return Navigator(
key: navigatorKey,
onGenerateRoute: (routeSettings) {
return MaterialPageRoute(
builder: (context) => child,
);
},
);
}
}
I tried to navigate with Navigator.push and Navigator.pushNamed, but it navigates inside SecondScreen without changing the BottomNavigationTabBars.
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => SecondScreen(),
),
);
Navigator.of(context).pushNamed(SecondScreen.route);
Also I can't use Provider, because I don't have access to _CustomNavigatorState class. Could anybody offer me any decision of the problem. Thanks.
I notice you have the nested Scaffolds, it's probably better to move your BottomNavigationBar to the outer Scaffold so you only have one Scaffold in your app. For the body of the outter Scaffold you will have your Stack
Regarding the navigator issues. The body of your app in a Stack with three offstage widgets. Only one of the widgets is visible at a time. When changing between each Offstage widget you don't actually navigate to it, all you have to do is change your _currentScreen to which ever you would like. So if you're on page one and would like to "push" to page 2 then have something like
onPressed: () {
SetState(() {
_currentScreen = FirstScreen.route;
}
}
Then when your body rebuilds from the setState it will set the FirstScreen to be onstage and all other screens to be offstage. Showing you the FirstScreen.
I develop an app using BLoC pattern.
In my app there are 2 routes, route A and B, and both of them access same data.
A problem caused when moving the routes as below.
Move to route B from route A that shows the data.
Update the data at route B.
Back to route A.
After moving back to route A, the StreamBuilder of showing the data never updates automatically.
How can I let the StreamBuilder update on resumed state?
Here are sample codes.
routeA.dart
class RouteA extends StatefulWidget {
#override
_RouteAState createState() => _RouteAState();
}
class _RouteAState extends State<RouteA> {
#override
Widget build(BuildContext context) {
final bloc = Bloc();
return Column(
children: [
StreamBuilder( // this StreamBuilder never updates on resumed state
stream: bloc.data, // mistake, fixed. before: bloc.count
builder: (_, snapshot) => Text(
snapshot.data ?? "",
)),
RaisedButton(
child: Text("Move to route B"),
onPressed: () {
Navigator.of(context).pushNamed("routeB");
},
),
],
);
}
}
routeB.dart
class RouteB extends StatefulWidget {
#override
_RouteBState createState() => _RouteBState();
}
class _RouteBState extends State<RouteB> {
#override
Widget build(BuildContext context) {
final bloc = Bloc();
return Center(
child: RaisedButton(
child: Text("Update data"),
onPressed: () {
bloc.update.add(null);
},
),
);
}
}
bloc.dart
class Bloc {
Stream<String> data;
Sink<void> update;
Model _model;
Bloc() {
_model = Model();
final update = PublishSubject<void>();
this.update = update;
final data = BehaviorSubject<String>(seedValue: "");
this.data = data;
update.map((event) => _model.update()).listen((event) => data.sink.add(_model.getData()));
}
}
model.dart
class Model {
static Model _model;
factory Model() { // model is singleton.
_model ??= Model._();
return _model;
}
Model._();
int _data = 0;
void update() {
_data++;
}
String getData() {
return _data.toString();
}
}
StreamBuilder updates the data whenever it gets changed not when just by calling stream
RouteA
class RouteA extends StatefulWidget {
#override
_RouteAState createState() => _RouteAState();
}
class _RouteAState extends State<RouteA> {
#override
Widget build(BuildContext context) {
return Column(
children: [
StreamBuilder( // this StreamBuilder never updates on resumed state
stream: bloc.data, // mistake, fixed. before: bloc.count
builder: (_, snapshot) => Text(
snapshot.data ?? "",
)),
RaisedButton(
child: Text("Move to route B"),
onPressed: () {
Navigator.of(context).push(MaterialPageRoute(builder: (ctx) {
return RouteB();
}));
},
),
],
);
}
}
Route B
class RouteB extends StatefulWidget {
#override
_RouteBState createState() => _RouteBState();
}
class _RouteBState extends State<RouteB> {
#override
Widget build(BuildContext context) {
return Center(
child: RaisedButton(
child: Text("Update data"),
onPressed: () {
bloc.updateData();
},
),
);
}
}
Bloc
class Bloc {
final _update = PublishSubject<String>();
Model _model = Model();
Stream<String> get data => _update.stream;
void updateData() async {
_model.update();
_update.sink.add(_model.getData());
_update.stream.listen((event) {
print(event);
});
}
dispose() {
_update.close();
}
}
final bloc = Bloc();
just follow above changes, it will do the trick for you.
I have a Stream Provider (connected to firebase) that is not working. I am guessing that the problem lies in the fact that I am using a named navigator [Navigator.pushNamed(context, '/route',)]. I guess this makes the 'route' widget to not be the son of the widget that calls it. Let me show it better below.
My app structure is as follows:
My main widget which handles routing and receives the Stream with user authentication (there is no problem here):
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return StreamProvider<User>.value(
value: AuthService().user,
child: MaterialApp(
debugShowCheckedModeBanner: false,
home: Wrapper(),
routes: {
'/home': (context) => Wrapper(),
'/edit_profile': (context) => UserProfile() //This is where I am having trouble.
}
),
);
}
}
The Wrapper that validates if the user is authenticated and acts accordingly:
class Wrapper extends StatelessWidget {
#override
Widget build(BuildContext context) {
final user = Provider.of<User>(context);
// return either the Home or Authenticate widget
if (user == null){
return Authenticate();
} else {
return HomeWrapper();
}
}
}
The HomeWrapper which receives the second stream and redirects to the widget I am having trouble with:
class HomeWrapper extends StatefulWidget {
#override
_HomeWrapperState createState() => _HomeWrapperState();
}
class _HomeWrapperState extends State<HomeWrapper> {
String currentBodyName = 'home';
Widget currentBodyWidget = Home();
#override
Widget build(BuildContext context) {
Widget _drawerOptions = Row(
children: [
FlatButton(child: someChild, onPressed: () {Navigator.pushNamed(context, '/edit_profile',);},), //This is the actual call to the navigator.
],
);
return StreamProvider<Map>.value( //This is the problematic Stream!
value: DatabaseService().userDetail,
child: Scaffold(
//Body
body: currentBodyWidget,
//I am simplifying this to show the most important parts
bottomNavigationBar: myBottomNavigationBar(
buttons: <Widget>[
FlatButton(
icon: someIcon,
onPressed: () => _onItemTapped('home'),
),
FlatButton(
icon: otherIcon,
onPressed: () => _onItemTapped('second_screen'),
),
],)
//Drawer
drawer: Drawer(child: _drawerOptions,), //This one has the call to the problematic edit_profile route.
);
}
void _onItemTapped(String newBodyName) {
if (newBodyName != currentBodyName){
setState(() {
currentBodyName = newBodyName;
switch(newBodyName) {
case 'home': {
currentBodyWidget = Home();
}
break;
case 'second_screen': {
currentBodyWidget = SecondScreen();
}
break;
default: {
currentBodyWidget = Home();
}
break;
}
});
}
}
}
Finally the edit_profile route calls the UserProfile Widget which looks like this:
class UserProfile extends StatefulWidget {
#override
_UserProfileState createState() => _UserProfileState();
}
class _UserProfileState extends State<UserProfile> {
#override
Widget build(BuildContext context) {
//This is where the error occurs!!
final userDocument = Provider.of<Map>(context) ?? [];
print(userDocument);
return Scaffold(body: Container());
}
}
This is the error that it throws:
The following ProviderNotFoundError was thrown building UserProfile(dirty, state: _UserProfileState#09125):
Error: Could not find the correct Provider<Map<dynamic, dynamic>> above this UserProfile Widget
Thank you very much!!
Turns out my approach was wrong.
Instead of wrapping the HomeWrapper with the StreamProvider, hoping that it would pass the data to the next route (UserProfile ), what I did was to wrap the UserProfile widget with a StreamProvider, as follows:
(Note: I changed the Map StreamProvider for a UserData StreamProvider.)
class UserProfile extends StatefulWidget {
#override
_UserProfileState createState() => _UserProfileState();
}
class _UserProfileState extends State<UserProfile> {
#override
Widget build(BuildContext context) {
final user = Provider.of<User>(context);
return StreamBuilder<UserData>(
stream: DatabaseService(uid: user.uid).userData,
builder: (context, snapshot) {
if (snapshot.hasData) {
UserData userData = snapshot.data;
return Scaffold(
body: Container(
//My Widget here
);
} else
return Loading();
});
}
}
This series was very helpful: https://www.youtube.com/playlist?list=PL4cUxeGkcC9j--TKIdkb3ISfRbJeJYQwC