Flutter: String parameter is not working? - flutter

I'm trying to pass the String parameter to the widgets but unfortunately it's not working and i don't know how is that happening because i have worked on the same parameter in another widget and it has worked :'(.
Here is my code:
MainPage.dart
//---------------------------------------------------------
//Assigned String from here and it works with the widgets except: MainHydrationProgressPage
//---------------------------------------------------------
late final String? tanksID = ModalRoute.of(context)?.settings.arguments as String?;
late final _pages = <Widget>[
MainTankHydrationPoolPage(tankID: tanksID,),
MainHydrationProgressPage(tanksID: tanksID,),
SummaryPage(tanksID: tanksID),
];
MainHydrationProgressPage.dart
import 'package:flutter/material.dart';
import 'package:smart_tank1/main_tank_detail_ui/hydration_progress/progress_view.dart';
import 'package:firebase_database/firebase_database.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter_locales/flutter_locales.dart';
class MainHydrationProgressPage extends StatefulWidget {
//-----------------
//Here is the String that i want to work on
//-----------------
final String? tanksID;
MainHydrationProgressPage({Key? key, required this.tanksID});
static const routeName = 'progress-screen';
#override
_MainHydrationProgressPageState createState() => _MainHydrationProgressPageState();
}
class _MainHydrationProgressPageState extends State<MainHydrationProgressPage> {
bool onAndOff = false;
late final dbRef = FirebaseDatabase.instance;
//final fStore = FirebaseFirestore.instance;
#override
void initState(){
dbRef;
//fStore;
super.initState();
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Padding(
padding: const EdgeInsets.only(bottom: 136, top: 32.0),
child: Column(
children: [
SizedBox(width: double.infinity),
LocaleText(
"waterPercentage",
style: Theme.of(context).textTheme.headline4,
),
Expanded(
//-------------------------------------------------------------------------------------------
//Trying to assign tanksID here but it's not working it keeps showing the error line under it
//-------------------------------------------------------------------------------------------
child: ProgressView(ID: tanksID),
),
SizedBox(height: 10,),
Padding(
padding: const EdgeInsets.only(left: 20.0, right: 10.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
LocaleText('pump', style: TextStyle(fontSize: 20),),
buildSwitch(),
],
),
),
],
),
),
);
}
Widget buildSwitch() => Transform.scale(
scale: 1.5,
child: Switch.adaptive(
activeColor: Colors.blueAccent,
activeTrackColor: Colors.blue.withOpacity(0.4),
inactiveThumbColor: Colors.white,
inactiveTrackColor: Colors.grey,
splashRadius: 50,
value: onAndOff,
onChanged: (value){
final user = FirebaseAuth.instance.currentUser;
setState(() {
onAndOff = value;
if (value) {
//--------------
//1. Firestore
//--------------
// fStore.collection('esp').doc(user.uid).update({
// 'pump': 'on',
// });
//-----------------
//2. Realtime database
//-----------------
dbRef.ref().child('users'). child(user!.uid).child('pump').set('on').asStream();
}else {
//--------------
//1. Firestore
//--------------
// fStore.collection('esp').doc(user.uid).update({
// 'pump': 'off',
// });
//-----------------
//2. Realtime database
//-----------------
dbRef.ref().child('users').child(user!.uid).child('pump').set('off').asStream();
}
},
);
}
),
);
}
Here is what it tells me: Undefined name 'tanksID'.
Try correcting the name to one that is defined, or defining the name.
Anyone faced this problem before? and how can it be solved!
I did flutter clean and flutter get packages then restarted vscode but it didn't work! :'(
please help

It worked with you before because maybe you were using a stateless widget or the variable defined in the state itself, but in this case you have a stateful widget and the variable is defined in the stateful widget not in the state itself, so you have to call it like: widget.tanksID

Related

Display Data Dynamically from Firebase Firestore to Flutter App

I am trying to create a Music Streaming App, I completed the UI with dummy data but now I want to show the data dynamically through firebase. Here's the App UI to better understand. The images should come from firebase document which looks something like this into a ListView which accepts certain parameters so that I won't have to assign the data to every single widget manually.
I have tried couple of codes but none of them worked... Here's the code.
class Home extends StatefulWidget {
#override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> {
late QuickPickProvider quickPickProvider;
Future<void> _handleRefresh() async {
return await Future.delayed(Duration(milliseconds: 2200));
}
#override
void initState() {
QuickPickProvider quickPickProvider = Provider.of(context, listen: false);
quickPickProvider.fetchQuickPicks();
super.initState();
}
#override
Widget build(BuildContext context) {
quickPickProvider = Provider.of(context);
return MaterialApp...
Here's the code I tried
Padding(
padding: const EdgeInsets.only(left: 15),
child: const Text('Quick Picks',
style: TextStyle(fontSize: 22),),),
const SizedBox(height: 20),
SizedBox(
height: 212,
width: MediaQuery.of(context).size.width,
child: ListView(
scrolldirection= axis.horizontal,
children: quickPickProvider.getQuickPicksDataList.map(
(quickPicksData) {
return Padding(
padding: const EdgeInsets.only(left: 15.0),
child: SizedBox(
width: 170,
child: Column( mainAxisAlignment:MainAxisAlignment.spaceBetween,
children: [
ClipRRect( borderRadius: const BorderRadius.all(Radius.circular(10),),
child: Image.network(quickPicksData.songImage,),
),
Column(
children: [
Text(quickPicksData.songName,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600),),
Text(
quickPicksData.artistName,
style: const TextStyle(
fontWeight:FontWeight.w500,
fontFamily:'Josefin_Sans')
),
]
),
]
),)
);}
).toList(),),),
...
This is the ticker provider file I am using. (I am using the provider package)
class QuickPickProvider with ChangeNotifier {
List<QuickPickModel> quickPickList = [];
late QuickPickModel quickPickModel;
fetchQuickPicks() async {
List<QuickPickModel> newList = [];
QuerySnapshot value =
await FirebaseFirestore.instance.collection("QuickPicks").get();
value.docs.forEach((element) {
quickPickModel = QuickPickModel(
songImage: element.get("songImage"),
artistName: element.get("artistName"),
songDuration: '00',
songLyrics: '00',
songName: element.get("songName"));
newList.add(quickPickModel);
});
quickPickList = newList;
notifyListeners();
}
List<QuickPickModel> get getQuickPicksDataList {
return quickPickList;
}
}
Here's the error
Error
Please help me with this code or else suggest any other method which is easy and efficient. I don't want to assign image manually to
Try wrap MaterialApp() with ChangeNotifierProvider():
ChangeNotifierProvider<QuickPickProvider>(
create: (_) => QuickPickProvider(),
child: MaterialApp(
debugShowCheckedModeBanner: false,
title: '',
),
)
You can first upload an image to Firebase Storage using:
File imageFile = YOURIMAGEHERE
TaskSnapshot snapshot = await FirebaseStorage.instance.ref('uniquePathForImage').putFile(imageFile);
Then get the url of the image you just uploaded with:
String url = await snapshot.ref.getDownloadURL();
Optionally, you can get an existing file with a url with:
String url = await FirebaseStorage.instance.getFromUrl('yourUrl').getDownloadURL();
This url works like any image url so you can use it with the Image.network widget:
Image.network(url)

error: The operator '[]' isn't defined for the type 'Object'. (undefined_operator at [lets_chat] lib/view/search.dart:34)

this is my search.dart file getting error in this
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:flutter/painting.dart';
import 'package:flutter/rendering.dart';
import 'package:lets_chat/services/database.dart';
import 'package:lets_chat/widgets/widget.dart';
class SearchScreen extends StatefulWidget {
#override
_SearchScreenState createState() => _SearchScreenState();
}
class _SearchScreenState extends State<SearchScreen> {
DatabaseMethods databaseMethods = new DatabaseMethods();
TextEditingController searchTextEditingController = new TextEditingController();
QuerySnapshot searchSnapshot;
initiateSearch(){
databaseMethods
.getUserByUsername(searchTextEditingController.text)
.then((val){
searchSnapshot = val;
});
}
Widget searchList(){
return ListView.builder(
itemCount:searchSnapshot.docs.length ,
itemBuilder: (context, index){
return SearchTile(
userName: searchSnapshot.docs[index].data()["name"],
userEmail: searchSnapshot.docs[index].data()["email"],
);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: appBarMain(context),
body: Container(
child: Column(
children: [
Container(
padding: EdgeInsets.symmetric(horizontal: 24, vertical: 16),
child: Row(
children: [
Expanded(
child: TextField(
controller: searchTextEditingController,
style: TextStyle(
color: Colors.orangeAccent
),
decoration: InputDecoration(
hintText: "search username...",
hintStyle: TextStyle(
color: Colors.orangeAccent
),
border: InputBorder.none
)
)
),
GestureDetector(
onTap: (){
initiateSearch();
},
child: Container(
padding: EdgeInsets.all(4),
child: Image.asset("assets/images/SearchIcon.png", height: 35, width: 40,)),
)
],
),
)
],
),
),
);
}
}
class SearchTile extends StatelessWidget {
final String userName;
final String userEmail;
SearchTile({this.userName, this.userEmail});
#override
Widget build(BuildContext context) {
return Container(
child: Row(
children: [
Column(
children: [
Text(userName, style: simpleTextStyle(),),
Text(userEmail, style: simpleTextStyle(),)
],
),
Spacer(),
Container(
decoration: BoxDecoration(
color: Colors.deepOrange,
borderRadius: BorderRadius.circular(30)
),
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Text("Message"),
)
],
),
);
}
}
someone please help i will be very thankful to u :)
error: The operator '[]' isn't defined for the type 'Object'. (undefined_operator at [lets_chat] lib/view/search.dart:34)
i will share other files too if needed please let me know about that
i am just a begginer so please help
We are just assigning the result value here and not updating the state.
It is supposed to be
initiateSearch(){
databaseMethods
.getUserByUsername(searchTextEditingController.text)
.then((val) {
setState(() { searchSnapshot = val; })
});
}
Also, initially the value of searchSnapshot might be null
because of that searchSnapshot.docs[index] here we might run into error saying you have called [].
Tip: Have a loading state which is false until it fetches from the database.
Like
bool isLoaded = false;
and while setting the state,
setState(() {
searchSnapshot = val;
isLoaded = true;
})
Refer: https://flutter.dev/docs/development/data-and-backend/state-mgmt
there is a syntax error in the lines
userName: searchSnapshot.docs[index].data["name"],
userEmail: searchSnapshot.docs[index].data["email"]
it should be
userName: searchSnapshot.docs[index].data["name"],
userEmail: searchSnapshot.docs[index].data["email"]

dependOnInheritedElement() was called before initstate() in flutter

I am currently having an issue while fetching a Provider' value ininitstate`.
I want to set a default value in dropdown in an Appbar and other parts in body. But I got an error saying dependOnInheritedElement() was called before initstate() in flutter.
My full code is below
main.dart
import 'package:test_eoil/model/button_data.dart';
import 'package:test_eoil/model/output_data.dart';
import 'screen/screen.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiProvider(providers: [
// ChangeNotifierProvider<ChordData>(create: (context) => ChordData()),
ChangeNotifierProvider<OutputData>(create: (context) => OutputData()),
ChangeNotifierProvider<ButtonData>(create: (context) => ButtonData())
],
child: MaterialApp(
home: Screen(),
),
);
}
}
screen.dart in screen folder
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:test_eoil/model/button_data.dart';
import 'package:test_eoil/model/output_data.dart';
class Screen extends StatefulWidget {
#override
_ScreenState createState() => _ScreenState();
}
class _ScreenState extends State<Screen> {
Widget dropdownWidget() {
return DropdownButton<Button>(
items: Provider.of<ButtonData>(context).buttons.map((Button value) {
return new DropdownMenuItem<Button>(
value: value,
child: new Text(value.type.toString()),
);
}).toList(),
onChanged: (Button newValue) {
Provider.of<ButtonData>(context).setSelectedItem(newValue);
},
value: Provider.of<ButtonData>(context).selectedButton,
);
}
#override
void initState() {
Provider.of<ButtonData>(context).selectedButton = Provider.of<ButtonData>(context).buttons.first;
super.initState();
}
#override
Widget build(BuildContext context) {
return Consumer<OutputData>(
builder: (context, outputData, child) => Scaffold(
appBar: AppBar(
title: Text("${Provider.of<ButtonData>(context).selectedButton}"), // new Text(widget.title), // "${Provider.of<ButtonData>(context).selectedButton.key}"
actions: <Widget>[
dropdownWidget(),
],
),
body: Column(
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
alignment: Alignment.centerRight,
padding: new EdgeInsets.symmetric(
vertical: 24.0, horizontal: 12.0),
child: outputData.outputs[0].output3.length > 2
? Text(
'${outputData.outputs[0].output3.substring(1, outputData.outputs[0].output3.length-1)}',
style: TextStyle(
fontSize: 35.0,
fontWeight: FontWeight.bold,
))
: Text('${outputData.outputs[0].output3}',
style: TextStyle(
fontSize: 35.0,
fontWeight: FontWeight.bold,
)),
),
Container(
alignment: Alignment.centerRight,
padding: new EdgeInsets.symmetric(
vertical: 24.0, horizontal: 12.0),
child: outputData.outputs[0].output2.length > 2
? Text(
'${outputData.outputs[0].output2.substring(1, outputData.outputs[0].output2.length-1)}',
style: TextStyle(
fontSize: 35.0,
fontWeight: FontWeight.bold,
))
: Text('${outputData.outputs[0].output2}',
style: TextStyle(
fontSize: 35.0,
fontWeight: FontWeight.bold,
)),
),
Container(
alignment: Alignment.centerRight,
padding: new EdgeInsets.symmetric(
vertical: 24.0, horizontal: 12.0),
child: Text('${outputData.outputs[0].output1}', // outputData.outputs[0].output1
style: TextStyle(
fontSize: 35.0,
fontWeight: FontWeight.bold,
)),
),
],
),
Expanded(
child: new Divider(),
),
Column(children: [
Row(children: [
buildButton("CLEAR"),
buildButton(""),
buildButton("PLAY"),
// buildButton("/")
]),
])
],
)));
}
Widget buildButton(String buttonText) {
return new Expanded(
child: new OutlineButton(
padding: new EdgeInsets.all(30.0),
child: new Text(
buttonText,
style: TextStyle(fontSize: 20.0),
),
onPressed: () => print("test"),
),
);
}
}
button_data.dart in model folder
import 'package:flutter/foundation.dart';
//import 'dart:collection';
class Button {
final int id;
final String type;
final String numberone;
final String numbertwo;
final String numberthree;
Button({this.id, this.type, this.numberone, this.numbertwo, this.numberthree});
}
class ButtonData extends ChangeNotifier {
List<Button> _buttons = [
Button(
type: "A",
numberone: "1",
numbertwo: "2",
numberthree: "3",
),
Button(
type: "B",
numberone: "A",
numbertwo: "B",
numberthree: "C",
),
];
List<Button> get buttons => _buttons;
Button _selectedButton;
Button get selectedButton => _selectedButton;
set selectedButton(Button button) {
_selectedButton = button;
notifyListeners();
}
void setSelectedItem(Button s) {
_selectedButton = s;
notifyListeners();
}
Button getKey(String value) {
return _buttons
.where((button) => button.type == value).first;
}
String getNumberOne(String value) {
return _buttons
.where((button) => button.type == value)
.map((button) => (button.numberone))
.toString();
}
String getNumberTwo(String value) {
return _buttons
.where((button) => button.type == value)
.map((button) => (button.numbertwo))
.toString();
}
String getNumberThree(String value) {
return _buttons
.where((button) => button.type == value)
.map((button) => (button.numberthree))
.toString();
}
}
output_data.dart in model folder
import 'package:flutter/foundation.dart';
class Output {
final int id;
String output1;
String output2;
String output3;
Output({this.id, this.output1, this.output2, this.output3});
}
class OutputData extends ChangeNotifier {
List<Output> _outputs = [
Output(output1: 'Hello', output2: 'Hi', output3: 'Nice'),
Output(output1: 'Haha', output2: 'Bye', output3: 'Sad'),
];
List<Output> get outputs {
return _outputs;
}
}
To be honest, I want to make it work without initstate() if possible(I heard that provider pattern doesn't need stful)
The reason I come up with an initstate() is this is the only solution (as much as I know) to set the default value in provider.
Hope you guys help me!
Issue is solved by adding Button_data constructor in button_data.dart.
ButtonData () {
_selectedButton = _buttons.first;
}
I also mentioned in my previous answer that Provider.of(context) is supposed to be used inside the widget tree, and anything that is outside of the build() method, is not in the widget tree. But if you still want to use it, then you need to set the listen parameter to false.
Like so:
#override
void initState() {
Provider.of<ButtonData>(context, listen: false).selectedButton = Provider.of<ButtonData>(context, listen: false).buttons.first;
super.initState();
}
But as you mentioned in your question, you don't want to use initState to set the default value. In every programming language, when you declare a variable with a value, that value becomes it's initial / default value, and you can change it later.
Instead of using initState, you can edit the following in your ButtonData class.
//Remove this.
List<Button> get buttons => _buttons;
Button _selectedButton;
Button get selectedButton => _selectedButton;
//Instead, Use this.
List<Button> get buttons => _buttons;
Button _selectedButton = _buttons.first;
Button get selectedButton => _selectedButton;
//This will declare the `selectedButton` variable with a default value.
//Happy coding! :)
Another solution when "dependOnInheritedElement() was called before initState()" could also be to access BuildContext safely in the initState method. It could be done by the following:
WidgetsBinding.instance.addPostFrameCallback((_) async {
// your code goes here
});
WidgetsBinding.instance.addPostFrameCallback((_) {
enter code here
});
This worked for me ->
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) async {
final authViewModel = Provider.of<AuthViewModel>(context, listen: false);
authViewModel.getToken();
});
}

Flutter Provider access via addPostFrameCallback says widget is outside the widget tree but flutter inspector shows otherwise

I am building my first big app in Flutter, and the first one where I need State Management, so I turned to Provider which is the recommended package to use for State Management. However I am having some issues where I declare my Providers in the main.dart file and down the tree I want to make changes and interact with one of the Providers but no matter what solution I try, I keep getting the same error: "Tried to listen to a value exposed with provider, from outside of the widget tree.". I get this error even though according the flutter inspector, the widget from where I am trying to make changes to the provider is inside of the widget tree (the "HomeScreen" screen is from where I am updating the provider).
Below I also share my code:
main.dart:
import 'package:flutter/material.dart';
import 'package:tic_tac_2/screens/welcome_screen.dart';
import 'package:provider/provider.dart';
import 'package:tic_tac_2/models/restaurants_data.dart';
import 'package:tic_tac_2/models/promotions_data.dart';
import 'package:tic_tac_2/models/user.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider<User>(create: (context) => User(),),
ChangeNotifierProvider<RestaurantsData>(create: (context) => RestaurantsData(),),
ChangeNotifierProvider<PromotionsData>(create: (context) => PromotionsData(),),
],
child: MaterialApp(
title: 'Tic Tac',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: WelcomeScreen(),
),
);
}
}
welcome_screen.dart:
import 'package:flutter/material.dart';
import 'package:animated_text_kit/animated_text_kit.dart';
import 'package:tic_tac_2/components/rounded_button.dart';
import 'login_screen.dart';
import 'register_screen.dart';
class WelcomeScreen extends StatelessWidget {
static const String id = 'welcome_screen';
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color(0xff000080),
body: Padding(
padding: EdgeInsets.symmetric(horizontal: 24.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Row(
children: <Widget>[
Hero(
tag: 'logo',
child: Container(
child: Image.asset('images/pin.png'),
height: 60.0,
),
),
TypewriterAnimatedTextKit(
text: ['Tic Tac'],
textStyle: TextStyle(
fontWeight: FontWeight.w900,
fontSize: 45.0,
color: Colors.white
),
),
],
),
SizedBox(
height: 48.0,
),
RoundedButton(
title: 'Entrar',
colour: Colors.lightBlueAccent,
onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (context) => LoginScreen()));
//Navigator.pushNamed(context, LoginScreen.id);
},
),
RoundedButton(
title: 'Registro',
colour: Colors.blueAccent,
onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (context) => RegistrationScreen()));
//Navigator.pushNamed(context, RegistrationScreen.id);
},
),
],
),
),
);
}
}
login_screen.dart:
import 'package:flutter/material.dart';
import 'package:tic_tac_2/components/rounded_button.dart';
import 'package:tic_tac_2/constants.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:modal_progress_hud/modal_progress_hud.dart';
import 'home_screen.dart';
import 'package:tic_tac_2/models/user.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:rflutter_alert/rflutter_alert.dart';
import 'package:email_validator/email_validator.dart';
final _firestore = Firestore.instance;
class LoginScreen extends StatefulWidget {
static const String id = 'login_screen';
#override
_LoginScreenState createState() => _LoginScreenState();
}
class _LoginScreenState extends State<LoginScreen> {
final _formKey = GlobalKey<FormState>();
bool showSpinner = false;
final _auth = FirebaseAuth.instance;
String email;
String password;
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: ModalProgressHUD(
inAsyncCall: showSpinner,
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 24.0),
child: Form(
key: _formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Flexible(
child: Hero(
tag: 'logo',
child: Container(
height: 200.0,
child: Image.asset('images/pin.png'),
),
),
),
SizedBox(
height: 48.0,
),
TextFormField(
validator: (val) => !EmailValidator.validate(val, true)
? 'Correo inválido'
: null,
keyboardType: TextInputType.emailAddress,
textAlign: TextAlign.center,
onChanged: (value) {
email = value;
},
decoration: kTextFieldDecoration.copyWith(
hintText: 'Escribe tu correo'),
),
SizedBox(
height: 8.0,
),
TextFormField(
validator: (val) =>
val.length < 6 ? 'La contraseña es muy corta' : null,
obscureText: true,
textAlign: TextAlign.center,
onChanged: (value) {
password = value;
},
decoration: kTextFieldDecoration.copyWith(
hintText: 'Escribe tu contraseña'),
),
SizedBox(
height: 24.0,
),
RoundedButton(
title: 'Entrar',
colour: Colors.lightBlueAccent,
onPressed: () async {
if (_formKey.currentState.validate()) {
setState(() {
showSpinner = true;
});
try {
final user = await _auth.signInWithEmailAndPassword(
email: email, password: password);
if (user != null) {
return _firestore
.collection('user')
.document(user.user.uid)
.get()
.then((DocumentSnapshot ds) {
User localUser = User(
uid: user.user.uid,
email: email,
role: ds.data['role']);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => HomeScreen(
user: user.user,
newUser: localUser,
)));
});
}
setState(() {
showSpinner = false;
});
} catch (e) {
setState(() {
showSpinner = false;
});
Alert(
context: context,
title: "Error en el registro",
desc: e)
.show();
print(e);
}
}
},
),
],
),
),
),
),
);
}
}
home_screen.dart:
import 'package:tic_tac_2/models/user.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'dart:async';
import 'package:tic_tac_2/models/restaurants_data.dart';
import 'package:provider/provider.dart';
import 'package:tic_tac_2/models/promotions_data.dart';
import 'package:tic_tac_2/widgets/RestaurantList.dart';
import 'package:geolocator/geolocator.dart';
Geoflutterfire geo = Geoflutterfire();
FirebaseUser loggedInUser;
User localUser;
class HomeScreen extends StatefulWidget {
final FirebaseUser user;
final User newUser;
const HomeScreen({Key key, this.user, this.newUser}) : super(key: key);
static const String id = 'home_screen';
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
final _firestore = Firestore.instance;
GoogleMapController mapController;
var pos;
Stream<dynamic> query;
StreamSubscription subscription;
#override
void dispose() {
// TODO: implement dispose
super.dispose();
subscription.cancel();
}
#override
void initState() {
// TODO: implement initState
super.initState();
if (localUser == null) {
localUser = widget.newUser;
loggedInUser = widget.user;
}
}
#override
Widget build(BuildContext context) {
void _getCurrentLocation(BuildContext context) async {
try {
Position position = await Geolocator()
.getCurrentPosition(desiredAccuracy: LocationAccuracy.low);
print('lat');
print(position.latitude);
print('lng');
print(position.longitude);
final QuerySnapshot restaurants = await _firestore.collection('restaurants').getDocuments();
for(var restaurant in restaurants.documents) {
print(restaurant);
Provider.of<RestaurantsData>(context).addRestaurant(
name: restaurant.data['name'],
owner: restaurant.data['owner'],
location: restaurant.data['location'],
uid: restaurant.data['uid'],
);
}
} catch (e) {
print(e);
}
}
WidgetsBinding.instance.addPostFrameCallback((_) => _getCurrentLocation(context));
print(Provider.of<RestaurantsData>(context).restaurants);
return Scaffold(
backgroundColor: Color(0xff000080),
body: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
padding: EdgeInsets.only(
top: 60.0,
bottom: 30.0,
left: 30.0,
right: 30.0,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
CircleAvatar(
child: Icon(
Icons.list,
size: 30.0,
color: Color(0xff000080),
),
backgroundColor: Colors.white,
radius: 30.0,
),
SizedBox(
height: 10.0,
),
Text(
'Tic Tac',
style: TextStyle(
fontSize: 50.0,
color: Colors.white,
fontWeight: FontWeight.w700,
),
),
Text(
'Restaurantes',
style: TextStyle(color: Colors.white, fontSize: 18.0),
)
],
),
),
Expanded(
child: Container(
padding: EdgeInsets.symmetric(horizontal: 20.0),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20.0),
topRight: Radius.circular(20.0),
),
),
child:
Provider.of<RestaurantsData>(context).restaurants.length > 0
? RestaurantList()
: Container(),
),
),
],
),
);
}
}
The thing causing the problem in the home_screen file, as far as I can tell, is the "getCurrentLocation(BuildContext context){}" function, and how and when I call it.
I have tried turning everything into statelessWidgets, calling the getLocation funtion without the "WidgetsBinding.instance.addPostFrameCallback(() => _getCurrentLocation(context));" line. I have tried not passing the context to the function, among other solutions that I have tried.
I really appreciate your help and I would like to thank you in advance. If you have any doubts regarding the code I will be more than happy to answer all of them.
Please understand the solution either on your own or via my explanation below. Don't just use my answer without understanding it. Although this is a simple flag you can just specify/flip, understanding it is the core of why Provider is even used.
New Solution
In your _getCurrentLocation method, which is hypothetically updated to the latest Provider pub version. Change:
Provider.of<RestaurantsData>(context).addRestaurant();
context.watch<RestaurantsData>().addRestaurant();
TO
Provider.of<RestaurantsData>(context, listen: false).addRestaurant();
context.read<RestaurantsData>().addRestaurant();
Drawing parallel to the old solution related to the old verison, read plays the same role as listen: false. Either is used to fix the OP's exception that's caused by watch playing the same role as listen: true. Important explanation on this can be found here and here. Thanks to user Vinoth Vino for alerting this new change via his comment.
Old Solution
In your _getCurrentLocation method, change
Provider.of<RestaurantsData>(context).addRestaurant()
to
Provider.of<RestaurantsData>(context, listen: false).addRestaurant()
Explanation
As the error illustrates
Tried to listen to a value exposed with provider, from outside of the widget tree.
You're getting notification update from your Provider instance from outside the widget tree. i.e. your Provider instance is calling Provider method NotifyListeners() which sends updates to all listeners. And this particular invocation in your question is listening to those updates, which is: Provider.of<RestaurantsData>(context)
This is happening because addPostFrameCallback is causing its parameter callback to be called outside your widget tree. This latter callback is encapsulating _getCurrentLocation local function. In turn this function has the Provider instance invocation. This sequence of events led the provider invocation to listen to updates outside the widget tree.
It's erroneous to listen to notification updates outside your widget tree e.g. user-action callbacks or initState.
To fix this issue, you need to assign listen flag to its non-default value false in code scopes outside your widget tree. e.g. initState or user-interaction callbacks or any code scope not directly under the widget's build method.
Provider Usage
This is how I use provider:
When watching/listening to Provider's values, Consumer in general and Selector for being picky/selective about when to cause a widget rebuild for performance reasons when you have a lot of Provider listen updates for different reasons and you just want to rebuild your widget tree for one particular reason. These methods for listening to changes are more versatile: makes it more clear which block of widgets are being rebuilt and also makes it's possible to access Provider without BuildContext e.g. from StatelessWidget or some helper method of a StatefulWidget that does not have a reference to BuildContext.
When reading/accessing Provider's values without caring about notifications/updates/changes to them. Then use Provider.of<T>(context, listen: false)
When using/calling Provider's services/methods and not values, use Provider.of<T>(context, listen: false).myMethod() e.g. Provider.of<RestaurantsData>(context, listen: false).addRestaurant() since most of the time you don't need to listen to Provider updates in this case.
Related References
To further understand listen flag behavior and the reasoning behind your exception, check out the GitHub docs here and source code docs. If you're REALLY interested, check this GitHub discussion.
To understand listen flag default value, check these author's issue comments here and here.

How to pass data back from a widget?

I have a screen where users can add a location. Here, I have separated all my widgets into there own files as illustrated below;
import 'package:cached_network_image/cached_network_image.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:fluttershare/pages/location/location_help_screen.dart';
import 'package:fluttershare/widgets/common_widgets/customDivider.dart';
import 'package:uuid/uuid.dart';
import '../../widgets/camp_type_select.dart';
import '../../widgets/extra_location_notes.dart';
import '../../widgets/location_input.dart';
import '../../widgets/opening_times.dart';
import '../../widgets/post_media.dart';
import '../../widgets/space_avalibility.dart';
import '../../widgets/utility_type_select.dart';
import '../../widgets/width_restriction.dart';
import '../../widgets/height_restriction.dart';
import '../../models/locations.dart';
import '../../models/user.dart';
import '../home.dart';
class AddNewLocation extends StatefulWidget {
static const routeName = '/add-new-location';
final User currentUser;
AddNewLocation({this.currentUser});
_AddNewLocationState createState() => _AddNewLocationState();
}
class _AddNewLocationState extends State<AddNewLocation> {
String postId = Uuid().v4();
final _scaffoldKey = GlobalKey<ScaffoldState>();
PlaceLocation _pickedLocation;
int storyPostCount = 0;
bool isLoading = false;
void _selectPlace(double lat, double lng) {
_pickedLocation = PlaceLocation(lattitude: lat, longitude: lng);
}
getLocationPostCount() async {
setState(() {
isLoading = true;
});
QuerySnapshot snapshot = await locationPostRef
.document(currentUser.id)
.collection('user_location_posts')
.getDocuments();
setState(() {
storyPostCount = snapshot.documents.length;
});
}
createLocationPostInFirestore(
{String mediaUrl,
String description,
double heightRestriction,
double widthRestriction}) {
locationPostRef
.document(currentUser.id)
.collection("user_location_posts")
.document(postId)
.setData({
"postId": postId,
"ownerId": currentUser.id,
"username": currentUser.username,
"description": description,
"timestamp": timestamp,
"lattitude": _pickedLocation.lattitude,
"longitude": _pickedLocation.longitude,
"max_height": heightRestrictionValue.toStringAsFixed(0),
"max_width": widthRestrictionValue.toStringAsFixed(0),
});
}
handlePostSubmit() {
createLocationPostInFirestore(
heightRestriction: heightRestrictionValue,
widthRestriction: widthRestrictionValue,
);
SnackBar snackbar = SnackBar(
content: Text("Profile Updated"),
);
_scaffoldKey.currentState.showSnackBar(snackbar);
setState(() {
postId = Uuid().v4();
});
}
buildUploadUserHeader() {
return Container(
margin: EdgeInsets.only(bottom: 10),
height: 200,
child: Row(
children: <Widget>[
Expanded(
flex: 2,
child: Container(
color: Colors.blue,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
ListTile(
leading: CircleAvatar(
backgroundImage:
CachedNetworkImageProvider(currentUser.photoUrl)),
),
],
),
),
),
Expanded(
flex: 6,
child: Container(
color: Colors.pink,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Text(currentUser.displayName),
],
),
),
),
],
),
);
}
buildCampUploadForm() {
return Container(
child: SingleChildScrollView(
child: Column(
children: <Widget>[
//buildUploadUserHeader(), //TODO: This is the profile header that is dissabled for now. Work on possibly a header in the future.
Container(
padding: EdgeInsets.all(15),
child: Column(
children: <Widget>[
CampTypeSelect(),
CustomDivider(),
LocationInput(_selectPlace),
CustomDivider(),
HeightRestriction(),
WidthRestriction(),
SpaceAvalibility(),
OpeningTimes(),
CustomDivider(),
PostMedia(),
CustomDivider(),
UtilityServices(),
CustomDivider(),
ExtraLocationNotes(),
Container(
height: 80,
margin: EdgeInsets.only(top: 10, bottom: 10),
child: Row(
children: <Widget>[
Expanded(
child: FlatButton(
color: Colors.black,
onPressed: () => handlePostSubmit(),
child: Text(
"SUBMIT",
style: Theme.of(context).textTheme.display2,
),
padding: EdgeInsets.all(20),
),
)
],
),
),
],
),
),
],
),
));
}
#override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(
automaticallyImplyLeading: false,
title: const Text(
'Add New Location',
style: TextStyle(color: Colors.black),
),
actions: <Widget>[
// action button
IconButton(
icon: Icon(Icons.info_outline),
color: Colors.black,
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
fullscreenDialog: true,
builder: (context) => LocationSubmitHelpScreen()),
);
},
),
// action button
IconButton(
icon: Icon(Icons.close),
color: Colors.black,
onPressed: () {
Navigator.of(context).pop();
},
),
],
),
body: buildCampUploadForm(),
backgroundColor: Colors.white,
);
}
}
What I am trying to do is pass the data back from the widget ExtraLocationNotes()
to the function createLocationPostInFirestore().
For context, this is what my widget looks like;
import 'package:flutter/material.dart';
import 'common_widgets/custom_form_card.dart';
class ExtraLocationNotes extends StatefulWidget {
_ExtraLocationNotesState createState() => _ExtraLocationNotesState();
}
class _ExtraLocationNotesState extends State<ExtraLocationNotes> {
TextEditingController descriptionController = TextEditingController();
#override
Widget build(BuildContext context) {
return CustomFormCard(
child: Column(
children: <Widget>[
Container(
child: Row(
children: <Widget>[
Text(
"EXTRA INFORMATION",
style: TextStyle(
fontSize: 18.0,
color: Colors.black,
fontWeight: FontWeight.w400,
letterSpacing: 2.0,
),
),
],
),
),
SizedBox(height: 20),
TextFormField(
controller: descriptionController,
maxLines: 6,
maxLength: 250,
maxLengthEnforced: true,
style:
new TextStyle(fontSize: 18.0, height: 1.3, color: Colors.black),
decoration: const InputDecoration(
hintText:
"Please write a description of this location for fellow travellers.",
alignLabelWithHint: true,
border: OutlineInputBorder(
borderRadius: BorderRadius.only(),
borderSide: BorderSide(color: Colors.black),
),
),
),
],
),
);
}
}
How do I pass the data back to the parent widget?
You need a callback, which will be triggered in the child widget then the value will be updated in the parent widget:
// 1- Define a pointers to executable code in memory, which is the callback.
typedef void MyCallback(String val);
class ExtraLocationNotes extends StatefulWidget {
// 2- You will pass it to this widget with the constructor.
final MyCallback cb;
// 3- ..pass it to this widget with the constructor
ExtraLocationNotes({this.cb});
_ExtraLocationNotesState createState() => _ExtraLocationNotesState();
}
class _ExtraLocationNotesState extends State<ExtraLocationNotes> {
//..
//...
RaisedButton(
//..
// 4- in any event inside the child you can call the callback with
// the data you want to send back to the parent widget:
onPressed: () {
widget.cb("Hello from the other side!");
}
),
}
Then inside the parent widget you need to catch the data which sent form the child:
class AddNewLocation extends StatefulWidget {
//...
_AddNewLocationState createState() => _AddNewLocationState();
}
class _AddNewLocationState extends State<AddNewLocation> {
// 1- Global var to store the data that we're waiting for.
String _dataFromMyChild = "";
buildCampUploadForm() {
return Container(
//...
//...
// 2- Pass the callback with the constructor of the child, this
// will update _dataFromMyChild's value:
ExtraLocationNotes(cb: (v) => setState(() => _dataFromMyChild = v)),
//..
}
// then
createLocationPostInFirestore() {
// Use _dataFromMyChild's value here
}
}
You can use the BuildContext object to get the context widget (might no be the parent!) couldn't read it all but as i understand that you need to pass the info from the child to the parent ,and you can do it with some like this :-
(context.widget as MyType).doStuff();
Note.
please check first with
print(context.widget.runtimeType);
but to make a better solution make a mutable data object that is passed from parent to the child so when changes happens it reflect's on the parent so you can separate business logic from ui logic.