In flutter_redux, how do I use StoreConnector to make a DropdownButton in a dialog?
I have a dialog in a stateful widget, so that when the DropdownButton changes, it gets updated rendered again.
Making the switch to Redux, I created two StoreConnectors, one for the selected value, one for the state.dispatch callback.
I had to add this code to the builder:
if(_selectedId == null) {
_selectedId = size;
}
If that's not there, either the drop down doesn't change as it always has the redux state value, or the redux state value doesn't show in the drop down even though it's there.
Widget build(BuildContext context) {
return SimpleDialog(
title: new Text("User Settings"),
children: <Widget>[
// Redux store connector, to listen on state.size only, not entire state
StoreConnector<AppState, dynamic>(
converter: (store) => store.state.size,
builder: (context, size) {
if(_selectedId == null) {
_selectedId = size;
}
print("prior to container: ${_selectedId}");
return new Container(
padding: const EdgeInsets.all(10.0),
child: new DropdownButton<String>(
hint: const Text("Select your size"),
value: _selectedId,
onChanged: (String changedValue) {
// this sets State for the parent widget, the dialog, so the
// dropdown runs it's render. It has nothing to do with
// redux, only updating the dropdown value
setState(() {
_selectedId = changedValue;
print(changedValue);
print(_selectedId);
});
},
items: _sizes,
)
);
} // builder
),
// need a second store connector, for dispatch ChangeSize action
StoreConnector<AppState, dynamic>(
converter: (store) {
return (size) => store.dispatch(ChangeSize(_selectedId));
},
builder: (context, callback) {
return new SimpleDialogOption(
onPressed: () {
// StoreConnector callback
callback(_selectedId);
// close dialog
Navigator.pop(context);
},
child: const Text('Close'),
);
} // builder
)
],
);
} //widget build
Related
I am trying to build an app with category list. I wanted to make the selected category bigger and give it a bold color. But every time i set state, it refreshes the screen and then it sets the state. Can i stop it from refreshing every time i set a state?
this is my code
This is my Future
Future getCategories() async{
var firestore = Firestore.instance;
QuerySnapshot querySnapshot = await firestore.collection("category").getDocuments();
return querySnapshot.documents;
}
and this is my future builder
FutureBuilder(
future: getCategories(),
builder: (_, snapshot){
if (snapshot.connectionState == ConnectionState.waiting) {
return Container(
child: Text('Loading'),
);
}else return ListView(
scrollDirection: Axis.horizontal,
children:
List.generate(snapshot.data.length, (int index) =>
InkWell(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(snapshot.data[index].data["category"],
style: TextStyle(fontSize: currentCategory == snapshot.data[index].data["category"] ? sActive : sNotActive,
color: currentCategory == snapshot.data[index].data["category"] ? active : notActive,)
,),
),
onTap: () {
String selectedCategory = snapshot.data[index].data["category"];
setState(() {
currentCategory = selectedCategory;
print(selectedCategory);
});
},
)));
})
When you use setState() the entire widget will rebuild and subsequently the FutureBuilder which will call the getCategories() method again.
You can call the getCategories() method (without awaiting it) in the initState() and save the future in a property of the state class and then use this property in the future builder instead of getCategories().
Another solution could be to move the ListView() in a separate widget, so you can rebuild only the ListView when you select an item and call setState.
Anyway you can use for example the BLoC pattern to manage the state, in this way you don't need to rebuild the entire widget.
I am working on a list app and want to load the categories of tasks of the home page in the dropdown menu button.
I have used SQflite to save the categories in a table.
When I tap the Dropdown Menu, the values seem not to be loaded on the menu. It's just an empty list.
Here is the code:
class _HomeState extends State<Home> {
TodoService _todoService;
var _selectedValue;
var _categories = List<DropdownMenuItem>();
List<Todo>_todoList=List<Todo>();
final GlobalKey<ScaffoldState> _globalKey=GlobalKey<ScaffoldState>();
#override
initState(){
super.initState();
getAllTodos();
}
_loadCategories() async {
var _categoryService = CategoryService();
var categories = await _categoryService.readCategory();
categories.forEach((category) {
setState(() {
_categories.add(DropdownMenuItem(
child: Text(category['name']),
value: category['name'],
));
});
});
}
getAllTodos()async{
_todoService=TodoService();
_todoList=List<Todo>();
var todos= await _todoService.readTodo();
todos.forEach((todo){
setState(() {
var model=Todo();
model.id=todo['id'];
model.title=todo['title'];
model.dueDate=todo['dueDate'];
model.category=todo['category'];
model.isFinished=todo['isFinished'];
_todoList.add(model);
});
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
key: _globalKey,
appBar: AppBar(
actions: <Widget>[
DropdownButton(
value: _selectedValue,
items: _categories,
onChanged: (value) {
setState(() {
_selectedValue = value;
_loadCategories();
});
},
),
Have you checked the service calls?
Maybe, the SQFlite database is not populated with the values you are trying to retrieve.
Try with a dummy set of values first, like:
items: <String>['One', 'Two', 'Free', 'Four']
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
If the above works, make sure you populate the table by calling an insert query via the service call.
Or you can use the SQFlite's build-in support for doing a raw insert. This means that you can use a SQL string using rawInsert().
You forgot to map the List<Object> in the items of DropdownButton.
DropdownButton _dropdownButton(List<Object> categoriesList) {
return DropdownButton(
value: _selectedValue,
onChanged: (dynamic newValue) {
setState(() {
_selectedValue = newValue;
});
},
items: categoriesList
.map<DropdownMenuItem<Object>>((Object value) {
return DropdownMenuItem<Object>(
value: value,
child: Text(value.getTxt()), // Replace this with getter for the value you want to display
);
}).toList(),
);
}
I'm trying to figure out how to render a list from a specific collection in firebase and to change that list when selecting options from dropdown menu. I could get the list rendered on 1 collection, but when I add my dropdown menu, with the default value being 'lost', nothing is displayed. Here's what I have so far that works, but not entirely what I want.
class _ListPageState extends State<ListPage>{
List<String> _type = ['lost', 'found'];
String _selectedView = 'lost';
//this getData pulls from 'lost' collection, since I set _selectedView to lost by default
Future getData() async{
var firestore = Firestore.instance;
QuerySnapshot qn = await firestore.collection(_selectedView).getDocuments();
return qn.documents;
}
navigateToDetail(DocumentSnapshot post){
Navigator.push(context, MaterialPageRoute(builder: (context) => DetailPage(post: post,)));
}
Widget _viewType() {
return new DropdownButtonFormField(
value: _selectedView,
onChanged: (newValue) {
setState(() {
_selectedView = newValue;
});
},
items: _type.map((view) {
return new DropdownMenuItem(
child: new Text(view),
value: view,
);
}).toList(),
);
}
#override
Widget build(BuildContext context){
return ListView(
children: <Widget>[
_viewType(),
FutureBuilder(//it's not rendering any of this when adding the dropdown above it
future: getData(),
builder: (_, snapshot){
if(snapshot.connectionState == ConnectionState.waiting){
return Center(
child: Text("Loading"),
);
}
else{
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (_, index){
return ListTile(
title: Text(snapshot.data[index].data["Title"]),
onTap: () => navigateToDetail(snapshot.data[index]),
);
});
}
}),]
);
}
}
Thanks in advance for any help.
Please let me know if there's any more code you'd like to see.
I this I have to wrap part of it with setState(), but I'm not quite sure where.
Thanks for the fast clarification.
What is happening here is that you have put a ListView inside a ListView. You should use a Column.
By default (as mentioned in the documentation):
The Column widget does not scroll (and in general it is considered an error to have more children in a Column than will fit in the available room). If you have a line of widgets and want them to be able to scroll if there is insufficient room, consider using a ListView.
In your case, you want to place a ListView that will overflow the Column that can't scroll. To avoid that, consider using an Expanded
to take the remaining space so that the height is somehow constrained and the ListView knows its limits and work properly.
class _ListPageState extends State<ListPage> {
List<String> _type = ['lost', 'found'];
String _selectedView = 'lost';
//this getData pulls from 'lost' collection, since I set _selectedView to lost by default
Future getData() async {
var firestore = Firestore.instance;
QuerySnapshot qn = await firestore.collection(_selectedView).getDocuments();
return qn.documents;
}
navigateToDetail(DocumentSnapshot post) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DetailPage(
post: post,
)));
}
Widget _viewType() {
return new DropdownButtonFormField(
value: _selectedView,
onChanged: (newValue) {
setState(() {
_selectedView = newValue;
});
},
items: _type.map((view) {
return new DropdownMenuItem(
child: new Text(view),
value: view,
);
}).toList(),
);
}
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
_viewType(),
Expanded(
child: FutureBuilder(
//it's not rendering any of this when adding the dropdown above it
future: getData(),
builder: (_, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(
child: Text("Loading"),
);
} else {
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (_, index) {
return ListTile(
title: Text(snapshot.data[index].data["Title"]),
onTap: () => navigateToDetail(snapshot.data[index]),
);
},
);
}
},
),
),
],
);
}
}
The feature is to display the profile screen of a user when a specific widget is tapped from a ListView. The way I want to assign each widget with unique ID is to retrieve all IDs from Firebase as an array and create the widgets by iterating through a for loop. Then I add the created widgets to a list and put that list into a ListView. The ListView builds correctly in the application, but I do not know how to add the unique ID's to the widgets in the ListView so that when they are tapped the profile of that widget's ID is retrieve and displayed in a new screen.
I may have the wrong idea about approaching this problem. Because otherwise the IDs will be rendered into the application when the user is using it, which may cause vulnerability issues.
I have tried to use the key property of widgets to store the user unique ID. I also searched the web if there is a way to assign an unique ID to a widget but no luck, or I jam not searching the right way.
enum ScreenStatus {
NOT_DETERMINED,
RETRIEVING_FRIEND_LIST,
FRIEND_LIST_READY,
FRIEND_PROFILE_OPEN
}
class FriendsScreen extends StatefulWidget {
FriendsScreen({this.auth});
final BaseAuth auth;
#override
State<StatefulWidget> createState() => _FriendsScreenState();
}
class _FriendsScreenState extends State<FriendsScreen> {
ScreenStatus screenStatus = ScreenStatus.NOT_DETERMINED;
List _friendList = [];
#override
void initState() {
super.initState();
widget.auth.retrieveUserDocument().then((document) {
document.data.forEach((fieldName, fieldValue) {
setState(() {
if (fieldName == 'friends') {
_friendList = fieldValue;
print(_friendList);
}
screenStatus = _friendList == null
? ScreenStatus.RETRIEVING_FRIEND_LIST
: ScreenStatus.FRIEND_LIST_READY;
});
});
});
}
Widget _createFriendListWidget(List friendList) {
List<Widget> list = List<Widget>();
for (int i = 0; i < friendList.length; i++) {
list.add(
Card(
child: InkWell(
onTap: () {
print('tapped');
},
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
ListTile(
leading: Icon(Icons.album),
title: Text(friendList[i]),
subtitle: Text('Subtitle'),
)
],
),
),
),
);
}
return ListView(children: list);
}
Widget _buildFriendList() {
return Scaffold(
body: Container(
child: _createFriendListWidget(_friendList),
),
);
}
Widget _buildWaitingScreen() {
return Scaffold(
body: Container(
alignment: Alignment.center,
child: CircularProgressIndicator(),
),
);
;
}
#override
Widget build(BuildContext context) {
switch (screenStatus) {
case ScreenStatus.NOT_DETERMINED:
return _buildWaitingScreen();
break;
case ScreenStatus.RETRIEVING_FRIEND_LIST:
return _buildWaitingScreen();
break;
case ScreenStatus.FRIEND_LIST_READY:
return _buildFriendList();
break;
default:
return _buildWaitingScreen();
}
}
}
The expected result is to open a new screen of the profile of the user when the widget is tapped with the respective ID of the user.
I don't think you need to identify widgets. Instead you can use user id in onTap closure
Here is how updated _createFriendListWidget could look:
Widget _createFriendListWidget(List friendList) {
List<Widget> list = List<Widget>();
for (int i = 0; i < friendList.length; i++) {
list.add(
Card(
child: ListTile(
leading: Icon(Icons.album),
title: Text(friendList[i]),
subtitle: Text('Subtitle'),
onTap: () {
print("tapped ${friendList[i].id}"); // assume that id is in .id field
}
),
),
);
}
return ListView(children: list);
}
Also note that I've removed Column, because it had only one child so it did not have any effect. And I removed Inkwell because ListView can handle onTap too.
I am trying to set up a toggle switch button from flutter to firestore. I have already set up the dependencies in my flutter project, however, I do not know how to connect the switch with the firestore.
I am trying to make an on/off switch which can be used to control light; I have tried giving it some values but, even then, I am not sure how to connect with firestore.
class _HomeState extends State<Home> {
bool _value = false;
void _onChanged(bool value) {
setState(() {
_value = value;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Home ${widget.user.email}'),
),
body: new Container(
padding: new EdgeInsets.all(32.0),
child: new Column(
children: <Widget>[
new SwitchListTile.adaptive(
title: new Text('Bedroom light'),
activeColor: Colors.red,
secondary: const Icon(Icons.lightbulb_outline),
value: _value,
onChanged: (bool value) {
_onChanged(value);
})
],
),
),
);
}
}
This is the code I have so far. I know that we have to use StreamBuilder but I would like to know how.
You have to create a database reference first in firestore say-
databaseReference = Firestore.instance.collection('Switches').where('switch','==',/*ANY NAME*/);
And then run a transition to update the value of value
Firestore.instance.runTransaction((transaction) async {
await transaction.update(
documentReference, _value);
};
Just make sure that in the firestore, the field which will take the value of _value isboolean