Passing Firestore document reference to flutter widget - firebase

I am having trouble passing a Firestore document reference to a widget in Flutter. The Idea is to have the class Home display a ListView of thumbnail images, which with the help of onTap route you to a VideoView that displays the video associated with the thumbnail image.
Now the Problem is that although I have all the classes and the layout set up, I am having trouble handing over the Firestore reference with the necessary metadata for the video to the VideoView class, like the url of the video in question or its title and comments.
All the thumbnail urls and the matching titles in the Home Widget are fetched from Firestore and added to the ListView as seen in the examples section here
My plan is to have apart from the title and thumbnailUrl field in Firestore also a reference to the matching Video document which holds all the metadata necessary to display the video and its comments in the VideoView widget.
Is this possible with the cloud_firestore plugin in flutter right now and if so, how could I implement it?Thanks in advance!

ListPage
ListTile(
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (context) => DetailsPage(msg: snapshot.data.documents[index]))),
)
DetailsPage
When using statefull widget
class DetailsPage extends StatefulWidget {
DocumentSnapshot msg;
DecryptorPage({#required this.msg});
#override
_DecryptorPageState createState() => _DecryptorPageState();
}
class _DecryptorPageState extends State<DecryptorPage> {
#override
Widget build(BuildContext context) {
final String msg = widget.msgs.data['msg'];
return Container(
child: Text('$msg)
);
}
}

Related

How can I use several providers for re-rendering a widget?

I'm building a Flutter app with Firebase and Riverpod.
Until the main page is displayed a user has to perform several steps to get there (e.g. sign in, validate email, get activated by admin, upload documents). For each of these steps i show a specific page or widget which is determined in AppRouterWidget (see below).
The problem i have is that i need at least 2 different providers to cover all possible states, since some aspects belong to the Firebase user in Authentication area and the others to the user account in Firebase's database (collection 'account'), which is of course only available if the user has logged in.
I can cover the authentication part, but i have no clue how i can add the user account part, which should be accessible by watching accountStreamProvider.
This is what i currently have working:
final accountStreamProvider = StreamProvider((ref) {
final database = ref.watch(databaseProvider)!;
return database.accountStream();
});
class AppRouterWidget extends ConsumerWidget {
const AppRouterWidget({Key? key}) : super(key: key);
#override
Widget build(BuildContext context, WidgetRef ref) {
final authStateChanges = ref.watch(authStateChangesProvider);
return authStateChanges.when (
data: (user) => _data(context, user, ref),
loading: () => const Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
),
error: (_, __) => const Scaffold(
body: Center(
child: Text('Error'),
),
),
);
}
Widget _data(BuildContext context, User? user, WidgetRef ref) {
// user is the auth user
if (user == null) {
// either login or signup
return const AuthPage();
} else {
// logged in. now check which step we have to show
if (!user.emailVerified) {
return const VerifyEmailPage();
} else {
// these are the account specific data
//final accountAsyncValue = ref.watch(accountStreamProvider);
//if (accountAsyncValue.hasValue || !accountAsyncValue.value!.isActiv) {
// return const WeCallYouWidget();
//}
}
return Container();
}
}
}
I guess that i need 2 listeners in build() and both would call _data when triggered, but i don't know exactly how to do this.
Thanks a lot for some insights.
I'd be tempted to move all the logic for deciding which page to show outside your widget.
One way to do this would be to create a StateNotifier<PageState> subclass (PageState could be a Freezed class or an enumeration) that takes all the repositories/data sources you need as arguments, subscribes to all the streams as needed, and computes the output state that the widget can watch as:
final pageState = ref.watch(pageStateProvider);
return pageState.when(
auth: () => ....
verifyEmail: () => ...
uploadDocuments: () => ...
// and so on
);
As a result i took bizz84 advice and moved all logic into a separate class which now holds all needed listeners.
Whenever a new event happens it can react to that notification and determine the new page state which will be used to show the correct page.

Flutter Update Text Widget Variable without Reloading

Im using Flutter with Firestore to make Investing Application.
enter image description here
In this page, I get Firestore's data in Streambuilder.
The data what i recieved are displayed in Listview with Inkwell.
What I want to do is when I click the Inkwell, then change the bottom left's Text Widget's text into Listview's number.
So, I used the setstate method with FutureBuilder.
It works but there are some problems.
When I click the Inkwell, then whole page reloads.
So, My Streambuilder widgets displays circleprogressindicator short.
However I think this is not good for users.
I want to solve this problem.
Is there any solution?
My code is in here.
I'm sorry for my poor English and I hope my problem was properly communicated.
https://github.com/RGLie/Investing-Game-ICISTS/blob/main/lib/startup_1_trade.dart
You can fix the rebuilds by storing the streams in variables outside of the build method and using them in the StreamBuilder widgets.
class Startup1Trade extends StatefulWidget {
...
}
class _Startup1TradeState extends State<Startup1Trade> {
...
CollectionReference prices = FirebaseFirestore.instance.collection('startup_1');
CollectionReference users = FirebaseFirestore.instance.collection('users');
final Stream priceStream = prices.doc('price').snapshots();
final Stream userStream = users.doc(widget.user.uid).snapshots();
#override
Widget build(BuildContext context) {
...
}
}
Use them like this:
StreamBuilder<DocumentSnapshot>(
stream: priceStream,
builder: (context, snap) {
...
}
}
StreamBuilder<DocumentSnapshot>(
stream: userStream,
builder: (context, snap) {
...
}
}

Getting a specific String from List.generate with onTap

I am using WordPress REST API to fetch posts from a WordPress site with this piece of code with the help of wordpress_api package.
class ProvideTitle with ChangeNotifier {
List<String> Titles = [];
List<String> Descriptions = [];
List<String> Urls = [];
void ClearTitle() { //clears lists when called
Titles.clear();
Descriptions.clear();
Urls.clear();
}
void TitleFetcher(term) async { //takes term from query and adds parameters
final String searchTerm=term;
final api = WordPressAPI('https://wordpresswebsite.com');
WPResponse res = await api.posts.fetch(args: {
"search" : searchTerm,
"per_page" : "20",
"page" : "1"
});
for (final post in res.data){ //returns String to fill Lists below for each post found
Titles.add(post.title);
Descriptions.add(post.content);
Urls.add(post.link);
}
notifyListeners();
}
}
Everything is working as expected and class notifies all listeners with each List<//Strings> respectively.
Problem here is I build a list with List.generate using List<//String> Title in search bar recommendations with material_floating_search_bar, code looks like this.
class SearchPage extends StatefulWidget {
const SearchPage({Key? key}) : super(key: key);
#override
State<StatefulWidget> createState() => SearchPageState();
}
class SearchPageState extends State<SearchPage> {//search bar from package mentioned
late String resultValue= "";
final controller = FloatingSearchBarController(); //controller for search bar
#override
Widget build(BuildContext context) {
return SafeArea(
child: Consumer<ProvideTitle>(builder: (context, provideTitle, child) {//Provider used for listening to ProvideTitle class
return FloatingSearchBar(
debounceDelay: const Duration(milliseconds: 165),
onQueryChanged: (query) async { //registers keyboard input
query != "" ? provideTitle.TitleFetcher(query) //sends input to TitleFetcher function inside ProvideTitle class
: provideTitle.ClearTitle(); //clears lists to stop showing fetced data with empty query(please feel free to recommend me a better way to do)
},
controller: controller,
hint: "Search",
backdropColor: Colors.transparent,
borderRadius: BorderRadius.circular(24),
physics: const BouncingScrollPhysics(),
builder: (context, _) => BuildBody(), //calls list builder as recommendations
body: resultValue != "" ? SearchResults(): //result body going to be called when some entry chosen from list
SizedBox());//page stays empty when there is no action
}),
);
}
Widget BuildBody() {
ProvideTitle model = Provider.of<ProvideTitle>(context); //ProvideTitle called for its lists as model
return ClipRRect(
borderRadius: BorderRadius.circular(8),
child: Material(
child: Column(
children:
List.generate(model.Titles.length, (index) => model.Titles[index].toString()) //list generated as long as Title[index] which is 20 max because we only call 20 post from api
.map((e) => ListTile(
onTap: () { // i want this onTap to register which tile I chosed
model.ClearTitle();//clears lists for next search
controller.close();//closes search bar recommendations
},
title: Text(e),
))
.toList(),
),
),
);
}
}
After that list generated based on search, I want to chose one of the tiles to build a widget that contains fetched content of that post.
I created a class for contents to be displayed based on the tile we've chosen earlier but everything is also okay with that class.
How can I effectively call my class to show tiles contents that I chosen earlier, preferably without further fetching to stop wasting server resources and my devices resources.
I am completely new at flutter and developing this app for not more than three weeks without any previous coding experience other than few basic java apps that works on console, so please please please feel free to correct me in any way rather it's about my problem or not. Much thanks in advance.
Also this is my first question on here so excuse if formatting is not adequate enough for site.
Using indexOf() successfully returns the int value of chosen tile.
List.generate(model.titles.length,
(index) => model.titles[index].toString())
.map((e) => ListTile(
onTap: () {
setState(() {});
controller.close();
model.resultValue = model.titles.indexOf(e); // we are returning the int value of chosen tile to another class here
model.parsData();
model.clearFetchedData();
},
title: Text(e),
))
.toList(),

Flutter Firebase Dynamic Link Navigator.push not navigating

I am trying to implement Firebase Dynamic links in a flutter. But when I click on the link it calls the functions but does not take me to the specified page.
Code Implementation
main.dart
Main Entry Final for Application
void main() {
Crashlytics.instance.enableInDevMode = true;
FlutterError.onError = Crashlytics.instance.recordFlutterError;
runZoned(() {
runApp(MyApp());
}, onError: Crashlytics.instance.recordError);
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
DynamicLinks dynamicLinks = new DynamicLinks();
dynamicLinks.initDynamicLinks(context);
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
return LayoutBuilder(builder: (context, constraints) {
return OrientationBuilder(builder: (context, orientation) {
SizeConfig().init(constraints, orientation);
return MaterialApp(
title: 'APP NAME',
theme: ThemeData(
primarySwatch: Colors.orange,
brightness: Brightness.light,
),
debugShowCheckedModeBanner: false,
home:SplashScreenMain(),
);
});
});
}
}
dynamicLinkManager.dart
Another class to handle Dynamic Links.
class DynamicLinks {
void initDynamicLinks(BuildContext context) async{
var data = await FirebaseDynamicLinks.instance.getInitialLink();
FirebaseDynamicLinks.instance.onLink(onSuccess: (dynamicLink) async {
print("Main = ${dynamicLink}");
var deepLink = dynamicLink?.link;
final queryParams = deepLink.queryParameters;
debugPrint('DynamicLinks onLink $deepLink');
print("queryParams $queryParams");
if(DynamicLinksConst.inviteUser == deepLink.path){
print("Step 1.......Code Works");
/* THIS PART CODE IS NOT WORKING */
Login.setActiveContext(context);
Navigator.push(context,
EaseInOutSinePageRoute(
widget: SignupPage()), //MaterialPageRoute
);
}else{
Navigator.push(context,
EaseInOutSinePageRoute(
widget: LoginPage()), //MaterialPageRoute
);
}
}, onError: (e) async {
debugPrint('DynamicLinks onError $e');
});
}
}
Console Output
Here is the output you can see that its returning data captured by dynamic link.
I Don't Think it a problem with firebase dynamic link it feels like more of a Navigator problem but I am unable to identify the problem here as this Navigator is working properly throughout the project expect here.
EaseInOutSinePageRoute just adds animation to navigations.
I/flutter ( 395): Main = Instance of 'PendingDynamicLinkData'
I/flutter ( 395): DynamicLinks onLink https://example.com/abc?para1=dataOne
I/flutter ( 395): queryParams {para1: dataOne}
I/flutter ( 395): Step 1.......Code Works
As mentioned in my comment, the issue here is that the expected BuildContext isn't used in Navigator.push().
Without a minimal repro, it's difficult to provide a concrete solution. Since you've mentioned that you're using an Authenticator class that pushes a new page/screen, it might be safer to manage the screen of the app in a single class. With this, it's easier to manage the BuildContext being used in Navigator.push(). You may check this sample in this blog post and see if it fits your use case.

Returning null user data from Firestore. How to reference it globaly instead?

I'm quite new to Flutter and I've been struggling to access a user's document on Firestore.
On the profile page,
I'm setting the current user's UID inside initState, but uid returns null for a quick second, then the page updates with correct info.
So I am able to retrieve a certain field (like displayName), but it isn't quite the best practice. I don't want to have a bunch of boilerplate code and await functions mixed with UI and such.
Code:
FirebaseUser user;
String error;
void setUser(FirebaseUser user) {
setState(() {
this.user = user;
this.error = null;
});
}
void setError(e) {
setState(() {
this.user = null;
this.error = e.toString();
});
}
#override
void initState() {
super.initState();
FirebaseAuth.instance.currentUser().then(setUser).catchError(setError);
}
Then in my body I have a Stream builder to get the document.
body: StreamBuilder(
stream: Firestore.instance
.collection('users')
.document(user.uid)
.snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(
valueColor:
AlwaysStoppedAnimation<Color>(Colors.deepOrange),
),
);
} else {
var userDocument = snapshot.data;
return showProfileHeader(userDocument);
}
},
)
I want to make 'global' references to be accessed throughout the app. Instead of getting the user's id on every page and streaming a specific field when I might need multiple ones.
The only ways I found online to do something similar, created lists with all the data in it. I feel like this might get extra fields I don't need.
How can I make data from Firestore available across the app?
I am using the "Provider" package for doing state management across my app. Nowadays its also the suggested way by the google flutter team when it comes to state management. See the package here: https://pub.dev/packages/provider
Regarding Firebase Auth and accessing the credentials application wide, i am using that said package like stated on this page:
https://fireship.io/lessons/advanced-flutter-firebase/
Short version below. Bootstrap your app like so:
import 'package:provider/provider.dart';
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
// Make user stream available
StreamProvider<FirebaseUser>.value(
stream: FirebaseAuth.instance.onAuthStateChanged),
// not needed for your problem but here you can see how
// to define other Providers (types) for your app.
// You need a counter class which holds your model of course.
ChangeNotifierProvider(builder: (_) => Counter(0)),
],
// All data will be available in this child and descendents
child: MaterialApp(...)
);
}
}
Then in your child widgets, just do:
// Some widget deeply nested in the widget tree...
class SomeWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
var user = Provider.of<FirebaseUser>(context);
return Text(user.displayName) // or user.uid or user.email....
}
}
This should do the trick.
That happens because FirebaseAuth.instance.currentUser() returns a future, and until that future is completed, you will not have the proper FirebaseUser object.
Making the user object global is not a bad idea. In addition, you can hook it up to the FirebaseAuth stream so that it gets updated everytime the user auth status changes, like so in a user.dart file:
class User {
static FirebaseUser _user;
static get user => _user;
static void init() async {
_user = await FirebaseAuth.instance.currentUser();
FirebaseAuth.instance.onAuthStateChanged.listen((firebaseUser) {
_user = firebaseUser;
});
}
}
You can call User.init() in main() and access the user object with User.user.

Resources