Assigning a field value from firestore to variable in flutter - flutter

I am trying to get four text field values from a specific firestore document, assign them to four different variables and use them in the build context.
This is what I have been trying to do for just one field value:
class PackageScanPage extends StatefulWidget {
#override
State<PackageScanPage> createState() => _PackageScanPageState();
}
class _PackageScanPageState extends State<PackageScanPage> {
#override
Widget build(BuildContext context) {
final _firestore = FirebaseFirestore.instance;
final textFieldValue = ModalRoute.of(context)!.settings.arguments;
var _message;
_fetchData() async{
_firestore.collection("groceries").document('$textFieldValue').get().then((value) {
setState(() {
_message = value.data['name'];
});
});
}
return Container(
child: Text(_message.toString())
),
}
Thanks in advance.

What do you mean by 'get four text field values from a specific firestore document, assign them to four different variables'
if you are trying to pass the text field value from the app to firebase then I got you
TextField(
onSubmitted: (value) async {
FirebaseFirestore.instance.runTransaction(
(Transaction myTransaction) async {
FirebaseFirestore.instance
.collection('example')
.doc('example')
.update({'something': value});
}

Related

Firebase: If I query a Firestore collection for a record and pass one column of data into a model, would my app do a second query for the next model?

I have a function called getNotifications that queries a collection in Firestore. I am running it on my Notifications screen.
On this screen, I want to optimize the number of Firestore querying to only query once. When the user gets to this screen, the app should query the data once, determine the notifID for the current index, then pass the initial data into the appropriate model. If the notifID == '1', then the initial data should be transformed via the GroupModel. If the notifID == '2', then transform via the FriendRequestModel. In doing all this, am I correct in assuming that Firestore will only query once, i.e. it will not re-query when passing the data through either the GroupModel or the FriendRequestModel? I'm worried because CommonModel only needs to read the notifID. I'm not even defining any other data fields in it, so I worry that this might signal to the Flutter framework that it needs to re-query.
notifications.dart
class ScreenNotifications extends StatefulWidget {
const ScreenNotifications({Key? key}) : super(key: key);
#override
State<ScreenNotifications> createState() => _ScreenNotificationsState();
}
class _ScreenNotificationsState extends State<ScreenNotifications> {
void initialize() async {
tempNotifsList = await database.getNotifications();
setState(() {
notifsList = tempNotifsList;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Notifications'),
),
body: Column(
children: [
Expanded(
child: ListView.builder(
itemCount: notifsList?.length ?? 0,
itemBuilder: (context, index) {
final notif = CommonModel.fromJson(data);
final notifID = notif.notifID;
if (notifID == '1') {
final group = GroupModel.fromJson(data);
}
if (notifID == '2') {
final friendRequest = FriendRequestModel.fromJson(data);
}
}
...//rest of code//
database.dart
Future<List> getNotifications() async {
final uid = getUID();
List notifsList = [];
FirebaseFirestore firestore = FirebaseFirestore.instance;
CollectionReference notifCollection = firestore.collection('notifications_' + uid);
final docsRef = await notifCollection.get();
docsRef.docs.forEach((element) {
Map<dynamic, dynamic> docMap = {'docID': element.id, 'data': element.data()};
notifsList.add(docMap);
});
return notifsList;
}
the best way to go about this is to the defined a notification type as part of fields while storing your notification,
"nofiType":....//here will be group of friends
so in your ListView.builder then you check if the notif.notiType is equl to the value show the widget

How to re-render a Widget based on another widget using riverpod in flutter?

I want to know how can I refresh a table data (which is fetched from an API using a future provider) and re-render the table widget based on dropdown value change.
Following is the Repo file with providers:
import 'package:ct_analyst_app/src/features/dashboard/domain/dashboard_data.dart';
import 'package:dio/dio.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../authentication/application/auth_local_service.dart';
abstract class IDashboardRepository {
Future<void> fetchDashboard(String name);
Future<void> fetchNames();
}
final clientProvider = Provider.family((ref, token) => Dio(BaseOptions(
baseUrl: "http://${dotenv.env['IP']}/excel/",
headers: {"authorization": token})));
class DashboardRepository implements IDashboardRepository {
DashboardRepository(this.read);
final Reader read;
DashboardData? _data;
DashboardData? get dashboardData => _data;
List<dynamic>? _names;
List<dynamic>? get names => _names;
#override
Future<DashboardData?> fetchDashboard(String name) async {
final token = await read(authServiceProvider).getToken();
final response = await read(clientProvider(token))
.get('/getData', queryParameters: {"name": name});
_data = DashboardData.fromJson(response.data);
print(name);
return _data;
}
#override
Future<void> fetchNames() async {
final token = await read(authServiceProvider).getToken();
final response = await read(clientProvider(token)).get('/analystNames');
_names = response.data["names"];
}
}
final dashboardRepositoryProvider =
Provider((ref) => DashboardRepository(ref.read));
final fetchDashboardData = FutureProvider.family<void, String>((ref, name) {
final repoProvider = ref.watch(dashboardRepositoryProvider);
return repoProvider.fetchDashboard(name);
});
final fetchAnalystNames = FutureProvider((ref) {
final repoProvider = ref.watch(dashboardRepositoryProvider);
return repoProvider.fetchNames();
});
I have tried to refresh the future provider in the dropdown onChange and it does fetch the new table data from the API. However, the widget which renders the data in the table is not getting re-rendered when the refresh is called.
Done as following:
onChanged: (String? newValue) {
ref.read(dropItemProvider.notifier).state = newValue as String;
ref.refresh(fetchDashboardData(newValue));
setState(() {
widget.value = newValue;
});
},
I am using ref.watch on the data, still it does not re-render the widget if the data is changed.
class TableGenerator extends ConsumerWidget {
const TableGenerator({Key? key}) : super(key: key);
#override
Widget build(BuildContext context, WidgetRef ref) {
final data = ref.watch(dashboardRepositoryProvider);
return data.dashboardData != null
? SingleChildScrollView(
scrollDirection: Axis.vertical,
child: Row(
children: [
const FixedColumnWidget(data: [
"one",
"two",
"three",
"four",
"fifth",
]),
ScrollableColumnWidget(
data: data.dashboardData as DashboardData)
],
))
: const CircularProgressIndicator();
}
}
Am I missing something or how should I approach this problem? like different providers or something else?
Thanks!
Your Widget is watching dashboardRepositoryProvider, which doesn't update after the ref.refresh call.
There's two things to consider:
dashboardRepository should just expose your repo / services, and instead it is used to observe actual data. This is not affecting your app directly, but it is part of the problem imho. I'd expect your Widget to observe a FutureProvider that exposes (and caches, etc.) the data by calling the methods inside your repository;
Then, let's analyze why your Widget isn't updating: dashboardRepository isn't depending, i.e. performing a watch, on the Provider you're refreshing, which is fetchDashboardData, nor it is depending on dropItemProvider (I am specifying this since your onChanged callback updates / refreshes two different Providers).
I think your should refactor your code so that it will expose actual data from a FutureProvider which exploits your repositories and can be simply refreshed similarly as what you already are doing.
Quick FutureProvider example:
// WARNING: PSEUDOCODE
final myDataProvider = FutureProvider<MyClass>((ref) {
final repo = ref.watch(myRepo);
final response = repo.getSomeData(...);
// TODO: add error handling, debouncing, cancel tokens, etc.
return MyClass.fromJson(response.data); // e.g.
});
Quick usage:
// WARNING: PSEUDOCODE
#override
Widget build(BuildContext context, WidgetRef ref) {
final myData = ref.watch(myDataProvider);
return ElevatedButton(
onTap: () {
ref.refresh(myDataProvider);
},
child: Text("Click me to refresh me (data: $myData)"),
);
}

I want to use data from a Future inside a ChangeNotifier Provider and a ListView

I can't figure out how to get the data from the myProvider before I call the getWalletItems(). Should I do 2 seperate providers??
My goal here is just to get all these items from a Future<List<Wallet'>> and return them into a listview that is able to have each item be selectable with a checkbox which will then pass on all the selected items to a different page. They will not be rebuilt there so I don't think I need another model but if I do just let me know. Here is my code for the ChangeNotifier:
class WalletModel extends ChangeNotifier {
List<Wallet> _wallet = [];
List<Wallet> get wallet => _wallet;
set wallet(List<Wallet> newValue) {
_wallet = newValue;
notifyListeners();
}
myProvider() {
loadValue();
}
Future<void> loadValue() async {
wallet = await WalletApi.getWalletItems();
}
UnmodifiableListView<Wallet> get allWalletItems =>
UnmodifiableListView(_wallet);
UnmodifiableListView<Wallet> get incompleteTasks =>
UnmodifiableListView(_wallet.where((_wallet) => !_wallet.isSelected));
UnmodifiableListView<Wallet> get completedTasks =>
UnmodifiableListView(_wallet.where((_wallet) => _wallet.isSelected));
void toggleWallet(Wallet wallet) {
final walletIndex = _wallet.indexOf(wallet);
_wallet[walletIndex].toggleSelected();
notifyListeners();
}
}
Here is the checkbox to select
Checkbox(
value: wallet.isSelected,
onChanged: (bool? checked) {
Provider.of<WalletModel>(context, listen: false)
.toggleWallet(wallet);
},
),
Here is the listview and if I need to post anyother code just let me know because I'm quite lost on what to do.
class WalletList extends StatelessWidget {
final List<Wallet> wallets;
WalletList({required this.wallets});
#override
Widget build(BuildContext context) {
return ListView(
children: getWalletListItems(),
);
}
List<Widget> getWalletListItems() {
return wallets
.map((walletItem) => WalletListItem(wallet: walletItem))
.toList();
}
}
make myProvider() a future and then use below code for WalletList Widget
before build runs for WalletList we want to get the items from the provider so we have used didChangedDependencies() as it runs before build and can be converted to future.
when the list is got we use the list that was set by above the make the UI
Note : Consumer changes its state whenever notifyListener() is called in Provider.
import 'package:flutter/material.dart';
class WalletList extends StatefulWidget {
#override
_WalletListState createState() => _WalletListState();
}
class _WalletListState extends State<WalletList> {
bool _isInit = true;
#override
void didChangeDependencies() async {
//boolean used to run the set list fucntion only once
if (_isInit) {
//this will save the incoming data to list before build runs
await Provider.of<WalletModel>(context, listen: false).myProvider();
_isInit = false;
}
super.didChangeDependencies();
}
#override
Widget build(BuildContext context) {
return Consumer<WalletModel>(builder: (context, providerInstance, _) {
return ListView(
children: providerInstance
.wallet
.map<Widget>((walletItem) => WalletListItem(wallet: walletItem))
.toList(),
);
});
}
// List<Widget> getWalletListItems() {
// return Provider.of<WalletModel>(context, listen: false)
// .wallet
// .map((walletItem) => WalletListItem(wallet: walletItem))
// .toList();
// }
}

Future Provider Stuck In loading state

I am using a future provider to display a login page on load and then a loading indicator on loading. Here is my future provider
final loginProvider = FutureProvider.family((ref, UserInput input) =>
ref.read(authRepositoryProvider).doLogin(input.email, input.password));
In my UI I have this....
class LoginScreen extends HookWidget {
final TextEditingController emailEditingController = TextEditingController();
final TextEditingController passwordEditingController =
TextEditingController();
#override
Widget build(BuildContext context) {
var userInput =
UserInput(emailEditingController.text, passwordEditingController.text);
final login = useProvider(loginProvider(userInput));
return login.when(
data: (user) => Login(emailEditingController, passwordEditingController),
loading: () => const ProgressIndication(),
error: (error, stack) {
if (error is DioError) {
return Login(emailEditingController, passwordEditingController);
} else {
return Login(emailEditingController, passwordEditingController);
}
},
);
}
}
here is my doLogin function.
#override
Future<dynamic> doLogin(String email, String password) async {
try {
final response = await _read(dioProvider)
.post('$baseUrl/login', data: {'email': email, 'password': password});
final data = Map<String, dynamic>.from(response.data);
return data;
} on DioError catch (e) {
return BadRequestException(e.error);
} on SocketException {
return 'No Internet Connection';
}
}
I would like to know why it's stuck in the loading state. Any help will be appreciated.
First off, family creates a new instance of the provider when given input. So in your implementation, any time your text fields change, you're generating a new provider and watching that new provider. This is bad.
In your case, keeping the UserInput around for the sake of accessing the login state doesn't make a lot of sense. That is to say, in this instance, a FamilyProvider isn't ideal.
The following is an example of how you could choose to write it. This is not the only way you could write it. It is probably easier to grasp than streaming without an API like Firebase that handles most of that for you.
First, a StateNotifierProvider:
enum LoginState { loggedOut, loading, loggedIn, error }
class LoginStateNotifier extends StateNotifier<LoginState> {
LoginStateNotifier(this._read) : super(LoginState.loggedOut);
final Reader _read;
late final Map<String, dynamic> _user;
static final provider =
StateNotifierProvider<LoginStateNotifier, LoginState>((ref) => LoginStateNotifier(ref.read));
Future<void> login(String email, String password) async {
state = LoginState.loading;
try {
_user = await _read(authRepositoryProvider).doLogin(email, password);
state = LoginState.loggedIn;
} catch (e) {
state = LoginState.error;
}
}
Map<String, dynamic> get user => _user;
}
This allows us to have manual control over the state of the login process. It's not the most elegant, but practically, it works.
Next, a login screen. This is as barebones as they get. Ignore the error parameter for now - it will be cleared up in a moment.
class LoginScreen extends HookWidget {
const LoginScreen({Key? key, this.error = false}) : super(key: key);
final bool error;
#override
Widget build(BuildContext context) {
final emailController = useTextEditingController();
final passwordController = useTextEditingController();
return Column(
children: [
TextField(
controller: emailController,
),
TextField(
controller: passwordController,
),
ElevatedButton(
onPressed: () async {
await context.read(LoginStateNotifier.provider.notifier).login(
emailController.text,
passwordController.text,
);
},
child: Text('Login'),
),
if (error) Text('Error signing in'),
],
);
}
}
You'll notice we can use the useTextEditingController hook which will handle disposing of those, as well. You can also see the call to login through the StateNotifier.
Last but not least, we need to do something with our fancy new state.
class AuthPage extends HookWidget {
const AuthPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final loginState = useProvider(LoginStateNotifier.provider);
switch (loginState) {
case LoginState.loggedOut:
return LoginScreen();
case LoginState.loading:
return LoadingPage();
case LoginState.loggedIn:
return HomePage();
case LoginState.error:
return LoginScreen(error: true);
}
}
}
In practice, you're going to want to wrap this in another widget with a Scaffold.
I know this isn't exactly what you asked, but thought it might be helpful to see another approach to the problem.

onFieldSubmitted of TextFormFeild repetitive call upon calling future function - Flutter

Scenario:
I have a TextFormFeild in which user write product name to search. If the searched product is not available in the database then I want to store that searched product in the database. To do that I have the function storeUserSearchedValue() which is calling future function inside it.
After looking googling this problem I have found that calling future during flutter's build phase causes repetitive invocations.
How can I store the value of the searched product in the database on the onFieldSubmitted function without facing non-stop repetirive invocation of the function?
Code
Class Displaying textfeild and and it's onFieldSubmitted function.
class SearchProductView extends StatefulWidget {
#override
_SearchProductViewState createState() => _SearchProductViewState();
}
class _SearchProductViewState extends State<SearchProductView> {
storeUserSearchedValue(String _userSearchedProduct,List<Product> _productsData) {
print("I N S I D E storeUserSearchedValue: " + _userSearchedProduct.toString());
int _noOfResultsOfUserProducts = 0;
for (int i = 0; i < _productsData.length; i++) {
if (_productsData[i].name.contains(_userSearchedProduct))
_noOfResultsOfUserProducts++;
}
if (_noOfResultsOfUserProducts == 0)
SearchviewModel().addCustomerSearchedProducts(_userSearchedProduct);
}
#override
Widget build(BuildContext context) {
final _allProductsData = Provider.of<List<Product>>(context);
return Scaffold(
............
TextFormField(
controller: _searchedProductText,
onFieldSubmitted:
storeUserSearchedValue(
_searchedProductText.text,
_allProductsData,
),
onChanged: searchOperation,
),
)
.........
}
Actual logic of storing data in the database
class SearchviewModel extends BaseViewModel {
// -------- Below Function are adding searched product in `customer` collection
addCustomerSearchedProducts(String searchedProductText) async {
String _currentUser = FirebaseAuth.instance.currentUser;
Map<String, dynamic> custdataMap = {
"custSearchedProducts": [searchedProductText, DateTime.now()]
};
await updateCustData(custdataMap, _currentUser.uid);
}
Future<bool> updateCustData(Map<String, dynamic> dataMap, String custID) async {
// - Dynamically adding data in the db
dataMap.forEach(
(key, value) async {
await FirebaseFirestore.instance.collection('customer').doc(custID).set(
{
key: value,
},SetOptions(merge: true),
);
},
);
return true;
}
}
Video representaion of problem
Simply adding this (_)=> (not this ()=>) with onFieldSubmitted property of textformfeild while calling storeUserSearchedValue function stop repetative invocations.
onFieldSubmitted: (_)=> storeUserSearchedValue(_searchedProductText.text,_allProductsData),
The above code snippet worked fine!
I'm not quite sure what is the reason behind it though