In my app, I do have a list, on which I have implemented the long press selection of this post of Raouf Rahiche. When the selection is enabled I do have a different appbar, that has an IconButton on it, that should disable the selection. But I do not know how to do that.
Till now it is not working the way it should. The behaviour is displayed in the video below.
The longpress-selection is a StatefulWidget:
class _SelectableItems extends State<SelectableItems> {
bool isSelected = false;
GoogleMaterialColors googleMaterialColors = new GoogleMaterialColors();
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return new GestureDetector(
onLongPress: () {
setState(() {
isSelected = !isSelected;
});
widget.callback();
},
onTap: () {
setState(() {
isSelected = !isSelected;
});
if (widget.longPressEnabled) {
widget.callback();
} else {
Navigator.push(
context,
MaterialPageRoute(builder: (context)=>RecipeDetails(widget.name))
);
}
},
child: ListTile(
leading: CircleAvatar(
child: (isSelected
? Icon(
Icons.check,
color: Colors.white,
)
: (widget.image != "no image"
? Container(
width: 40.0,
height: 40.0,
decoration: new BoxDecoration(
image: new DecorationImage(
colorFilter: ColorFilter.mode(Colors.black.withOpacity(0.2), BlendMode.darken),
image: AssetImage(widget.image),
fit: BoxFit.cover,
),
borderRadius: new BorderRadius.all(new Radius.circular(50.0)),
),
)
: Text(
widget.name[0].toUpperCase(),
style: TextStyle(
color: Colors.white,
fontSize: 21.0,
fontWeight: FontWeight.w400
),
)
)
),
backgroundColor: (isSelected
? googleMaterialColors.primaryColor()
: widget.color.withOpacity(1.00)
)
),
title: Padding(
padding: EdgeInsets.only(top: 25.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
widget.title
],
),
),
),
);
}
}
I am calling this widget inside a SideHeaderListView like this:
bool longPressFlag = false;
List<String> indexList = new List();
//other code
return SideHeaderListView(
hasSameHeader: (int a, int b){
return snapshot.data[a].name[0] == snapshot.data[b].name[0];
},
itemCount: snapshot.data.length,
headerBuilder: (BuildContext context, int index){
return new Padding(
padding: EdgeInsets.only(top: 30.0, left: 20.0, right: 25.0),
child: Container(
width: 10.0,
child: Text(
snapshot.data[index].name[0].toUpperCase(),
style: TextStyle(
color: googleMaterialColors.primaryColor().withGreen(120),
fontFamily: "Google-Sans",
fontSize: 15.0,
fontWeight: FontWeight.w600
),
),
),
);
},
itemExtend: 70.0,
itemBuilder: (BuildContext context, int index){
Color usedColor = convertColor.convertToColor(snapshot.data[index].backgroundColor);
String image = snapshot.data[index].image;
return SelectableItems(
color: usedColor,
name: snapshot.data[index].name,
title: (searchController.text.isEmpty
? Text(snapshot.data[index].name)
: recipeName(searchCondition, snapshot.data[index].name)
),
index: index,
image: image,
longPressEnabled: longPressFlag,
//isSelected: selectedFlag,
callback: () {
if (indexList.contains(snapshot.data[index].name)) {
indexList.remove(snapshot.data[index].name);
} else {
indexList.add(snapshot.data[index].name);
}
longPress();
},
);
},
);
void longPress() {
setState(() {
if (indexList.length == 0) {
longPressFlag = false;
} else {
longPressFlag = true;
}
});
}
I hope somebody would be able to solve my problem. Thanks in advance.
The first thing is that you should add each item a key in constructor like this :
MyItem({Key key}): super(key: key);
Why a key ?
A key allow you to identify your widget correctly.
See in doc :
A new widget will only be used to update an existing element if its
key is the same as the key of the current widget associated with the
element.
Create a GlobalKey (a GLobal key extends Key)
For each item to access the widget from, create a global key.
From the doc :
A key that is unique across the entire app. Global keys uniquely
identify elements. Global keys provide access to other objects that
are associated with elements, such as the a [BuildContext] and, for
[StatefulWidget]s, a [State].
Add in the code the creation of a global key for each item (in your SelectableItem for you) :
...
var key = new GlobalKey<SelectableItem >();
this.items.put(position, key);
return new SelectableItem(key: key,...);
Items is a map where you can save position and Global Key.
Now when you want to select a View from the parent just access the globalKey from the map of items and access the widget to do what you want.(update, uncheck, etc...)
Edit : exemple :
class SideHeaderListView {
Map<int, GlobalKey<_SelectableItems>> map = new Map();
create() {
for (int i = 0; i< 10; i++) {
var key = new GlobalKey<_SelectableItems>();
var item = new SelectableItems(key: key);
map.putIfAbsent(i, () => key);
}
}
redrawItem(int i) {
var widget = this.map[i].currentState;
widget.redraw();
}
}
class SelectableItems extends StatefulWidget {
SelectableItems({key: Key}) : super(key: key);
#override
State<StatefulWidget> createState() {
return new _SelectableItems();
}
}
class _SelectableItems extends State<SelectableItems> {
#override
Widget build(BuildContext context) {
return new Text("test");
}
redraw() {
setState(() {
});
}
}
You have commented part of code - //isSelected: selectedFlag,
I think, you have to add this field to your widget
class SelectableItems extands StatefulWidget {
SelectableItems({this.isSelected = false});
final bool isSelected;
...
class _SelectableItems extends State<SelectableItems> {
bool isSelected;
#override
void initState() {
isSelected = widget.isSelected ?? false;
super.initState();
}
....
And when you're creating list of items:
return SelectableItems(
...
isSelected: indexList.contains(snapshot.data[index].name)
I think this could work
Related
I'm trying to change my icon after I tap on my List Item. I already tried different things: I tried the onTap method but the icon just does not want to change. I'm very new to flutter and I would love to find some help for my problem :). Here is my code.
I already searched for solutions but I didn't got it working in my project
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'To-Do List',
theme: ThemeData(
primaryColor: Colors.white,
brightness: Brightness.dark,
),
home: Scaffold(
appBar: AppBar(title: Text('To-Do List'),
backgroundColor: Colors.amber,
),
body: BodyLayout(),
),
);
}
}
class BodyLayout extends StatefulWidget {
#override
BodyLayoutState createState() {
return new BodyLayoutState();
}
}
class BodyLayoutState extends State<BodyLayout> {
// The GlobalKey keeps track of the visible state of the list items
// while they are being animated.
final GlobalKey<AnimatedListState> _listKey = GlobalKey();
// backing data
List<String> _data = [];
final _isdone = Set<String>();
// bool selected = false;
List<bool> selected = new List<bool>();
Icon notdone = Icon(Icons.check_box_outline_blank);
Icon done = Icon(Icons.check_box);
TextEditingController todoController = TextEditingController();
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
SizedBox(
height: 445,
child: AnimatedList(
// Give the Animated list the global key
key: _listKey,
initialItemCount: _data.length,
// Similar to ListView itemBuilder, but AnimatedList has
// an additional animation parameter.
itemBuilder: (context, index, animation) {
// Breaking the row widget out as a method so that we can
// share it with the _removeSingleItem() method.
return _buildItem(_data[index], animation);
},
),
),
TextField(
controller: todoController,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'To-Do'
),
),
RaisedButton(
child: Text('Insert item', style: TextStyle(fontSize: 20)),
onPressed: () {
_insertSingleItem();
},
),
RaisedButton(
child: Text('Remove item', style: TextStyle(fontSize: 20)),
onPressed: () {
_removeSingleItem();
},
)
],
);
}
// This is the animated row with the Card.
Widget _buildItem(String item, Animation animation) {
final isdone = _isdone.contains(item);
selected.add(false);
return SizeTransition(
sizeFactor: animation,
child: Card(
child: ListTile(
title: Text(
item,
style: TextStyle(fontSize: 20),
),
trailing: Icon(
isdone ? Icons.check_box: Icons.check_box_outline_blank
),
onTap: (){
setState(() {
});
},
),
),
);
}
void _insertSingleItem() {
int insertIndex = 0;
setState(() {
_data.insert(0, todoController.text);
});
// Add the item to the data list.
// Add the item visually to the AnimatedList.
_listKey.currentState.insertItem(insertIndex);
}
void _removeSingleItem() {
int removeIndex = 0;
// Remove item from data list but keep copy to give to the animation.
String removedItem = _data.removeAt(removeIndex);
// This builder is just for showing the row while it is still
// animating away. The item is already gone from the data list.
AnimatedListRemovedItemBuilder builder = (context, animation) {
return _buildItem(removedItem, animation);
};
// Remove the item visually from the AnimatedList.
_listKey.currentState.removeItem(removeIndex, builder);
}
}```
You have already mentioned the icons above. You simply need to use them instead of declaring new ones again.
// This is the animated row with the Card.
Widget _buildItem(String item, Animation animation) {
final isdone = _isdone.contains(item);
selected.add(false);
return SizeTransition(
sizeFactor: animation,
child: Card(
child: ListTile(
title: Text(
item,
style: TextStyle(fontSize: 20),
),
trailing: isdone ? done: notdone, // use the icon variables you have already defined
onTap: (){
setState(() {
// add the item to _isdone set if it is not added and remove it if it is added when tapped on the list item
if(isdone) {
_isdone.remove(item);
} else {
_isdone.add(item);
}
});
},
),
),
);
}
In this code, I have added the item and removed the item in setSate() in the onTap(), so that whenever you tap the list item, _isdone Set gets updated and the build() is reloaded. Which makes your layout and data update itself every time you tap on the list item.
I am stacked!! and i know i will find help here . I create a flutter application which fetches data from news.org API . Everything was working fine until i started implementing BLOC in the app . I have successfully implemented the first part with BLOC with fetches all the data from the API . the next thing to do is to fetch another data using the categories provided by the API in another page using BLOC .
For instance , there are categories like business , technology , finance etc . So main thing is when the user taps on any of the category the data show be fetched from the API using BLOC .
the following are the codes for the bloc ...
THIS IS THE ERROR I GET
════════ Exception caught by widgets library ═══════════════════════════════════════════════════════
The following assertion was thrown building BlocListener<ArticleBloc, ArticleState>(dirty, state: _BlocListenerBaseState<ArticleBloc, ArticleState>#aae07):
A build function returned null.
The offending widget is: BlocListener<ArticleBloc, ArticleState>
Build functions must never return null.
To return an empty space that causes the building widget to fill available room, return "Container()". To return an empty space that takes as little room as possible, return "Container(width: 0.0, height: 0.0)".
The relevant error-causing widget was:
BlocListener<ArticleBloc, ArticleState> file:///C:/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_bloc-6.1.1/lib/src/bloc_builder.dart:149:12
When the exception was thrown, this was the stack:
#0 debugWidgetBuilderValue. (package:flutter/src/widgets/debug.dart:302:7)
#1 debugWidgetBuilderValue (package:flutter/src/widgets/debug.dart:323:4)
#2 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:4632:7)
#3 StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:4800:11)
#4 Element.rebuild (package:flutter/src/widgets/framework.dart:4343:5)
...
════════════════════════════════════════════════════════════════════════════════════════════════════
RepositoRy
abstract class CategoryRepository {
Future<List<Article>> getCategory(String category);
}
class CatService implements CategoryRepository {
#override
Future<List<Article>> getCategory(String category) async {
// List<Article> categoryNewsList = [];
String url =
"http://newsapi.org/v2/top-headlines?country=us&category=$category&apiKey=df74fc47f0dd401bb5e56c34893a7795";
return getData(url);
/*var response = await http.get(url);
//decode the response into a json object
var jsonData = jsonDecode(response.body);
//check if the status of the response is OK
if (jsonData["status"] == "ok") {
jsonData["articles"].forEach((item) {
//check if the imageUrl and description are not null
if (item["urlToImage"] != null && item["description"] != null) {
//create an object of type NewsArticles
Article newsArticleModel = new Article(
author: item["author"],
title: item["title"],
description: item["description"],
url: item["url"],
urlToImage: item["urlToImage"],
content: item["content"]);
//add data to news list
categoryNewsList.add(newsArticleModel);
}
});
}
return categoryNewsList;*/
}
}
Future<List<Article>> getData(String url) async {
List<Article> items = [];
var response = await http.get(url);
//decode the response into a json object
var jsonData = jsonDecode(response.body);
//check if the status of the response is OK
if (jsonData["status"] == "ok") {
jsonData["articles"].forEach((item) {
//check if the imageUrl and description are not null
if (item["urlToImage"] != null && item["description"] != null) {
//create an object of type NewsArticles
Article article = new Article(
author: item["author"],
title: item["title"],
description: item["description"],
url: item["url"],
urlToImage: item["urlToImage"],
content: item["content"]);
//add data to news list
items.add(article);
}
});
}
return items;
}
Bloc
class ArticleBloc extends Bloc<ArticleEvent, ArticleState> {
CategoryRepository categoryRepository;
ArticleBloc({this.categoryRepository}) : super(ArticleInitial());
#override
Stream<ArticleState> mapEventToState(
ArticleEvent event,
) async* {
if (event is GetArticle) {
try {
yield ArticleLoading();
final articleList =
await categoryRepository.getCategory(event.category);
yield ArticleLoaded(articleList);
} catch (e) {
print(e.message);
}
}
}
}
Event
class GetArticle extends ArticleEvent{
final String category;
GetArticle(this.category);
}
States
#immutable
abstract class ArticleState {
const ArticleState();
}
class ArticleInitial extends ArticleState {
const ArticleInitial();
}
class ArticleLoading extends ArticleState {
const ArticleLoading();
}
class ArticleLoaded extends ArticleState {
final List<Article> articleList;
ArticleLoaded(this.articleList);
}
class ArticleError extends ArticleState {
final String error;
ArticleError(this.error);
#override
bool operator ==(Object object) {
if (identical(this, object)) return true;
return object is ArticleError && object.error == error;
}
#override
int get hashCode => error.hashCode;
}
UI
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'News app',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: BlocProvider(
child: TestCat(),
create: (context) => ArticleBloc(categoryRepository : CatService()),
),
);
}
}
tEST category page
class TestCat extends StatefulWidget {
#override
_TestCatState createState() => _TestCatState();
}
class _TestCatState extends State<TestCat> {
bool isLoading = true;
List<String> categoryItems;
#override
void initState() {
super.initState();
categoryItems = getAllCategories();
// getCategory(categoryItems[0]);
// getCategoryNews();
}
getCategory(cat) async {
context.bloc<ArticleBloc>().add(GetArticle(cat));
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: header(context, isAppTitle: false, title: "App"),
body: _newsBody(context),
);
}
_newsBody(context) {
return ListView(
children: [
//category list
Container(
padding:
EdgeInsets.symmetric(horizontal: NewsAppConstants().margin16),
height: NewsAppConstants().columnHeight70,
child: ListView.builder(
itemCount: categoryItems.length,
itemBuilder: (context, index) {
return TitleCategory(
title: categoryItems[index],
onTap: ()=> callCat(context, categoryItems[index]),
);
},
shrinkWrap: true,
scrollDirection: Axis.horizontal,
),
),
Divider(),
Container(
child: BlocBuilder<ArticleBloc, ArticleState>(
builder: (context, ArticleState articleState) {
//check states and update UI
if (articleState is ArticleInitial) {
return buildInput(context);
} else if (articleState is ArticleLoading) {
return Loading();
} else if (articleState is ArticleLoaded) {
List<Article> articles = articleState.articleList;
updateUI(articles);
} else if (articleState is ArticleError) {
// shows an error widget when something goes wrong
final error = articleState.error;
final errorMsg = "${error.toString()}\nTap to retry";
ShowErrorMessage(
errorMessage: errorMsg,
onTap: getCategory,
);
}
return buildInput(context);
}),
),
],
);
}
getAllCategories() {
List<String> categoryList = [
"Business",
"Entertainment",
"General",
"Sports",
"Technology",
"Health",
"Science"
];
return categoryList;
}
Widget updateUI(List<Article> newsList) {
return SingleChildScrollView(
child: Column(
children: [
Container(
child: ListView.builder(
physics: ClampingScrollPhysics(),
shrinkWrap: true,
itemCount: newsList.length,
itemBuilder: (context, index) {
return NewsBlogTile(
urlToImage: newsList[index].urlToImage,
title: newsList[index].title,
description: newsList[index].description,
url: newsList[index].url,
);
}),
),
Divider(),
],
));
}
buildInput(context) {
ListView.builder(
itemCount: categoryItems.length,
itemBuilder: (context, index) {
return TitleCategory(
title: categoryItems[index],
onTap: () {
print("tapped");
// callCat(context, categoryItems[index]);
},
);
},
shrinkWrap: true,
scrollDirection: Axis.horizontal,
);
}
callCat(BuildContext context, String cat) {
print(cat);
context.bloc<ArticleBloc>().add(GetArticle(cat));
}
}
//this displays the data fetched from the API
class NewsBlogTile extends StatelessWidget {
final urlToImage, title, description, url;
NewsBlogTile(
{#required this.urlToImage,
#required this.title,
#required this.description,
#required this.url});
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {},
child: Expanded(
flex: 1,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
margin: EdgeInsets.all(NewsAppConstants().margin8),
child: Column(
children: <Widget>[
ClipRRect(
borderRadius:
BorderRadius.circular(NewsAppConstants().margin8),
child: Image.network(urlToImage)),
Text(
title,
style: TextStyle(
fontWeight: FontWeight.w600,
color: Colors.black,
fontSize: NewsAppConstants().margin16),
),
SizedBox(
height: NewsAppConstants().margin8,
),
Text(
description,
style: TextStyle(color: Colors.black54),
)
],
),
),
Divider(),
],
),
),
);
}
}
//news title category
class TitleCategory extends StatelessWidget {
final title;
final Function onTap;
TitleCategory({this.title, this.onTap});
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () => onTap,
child: Container(
margin: EdgeInsets.all(NewsAppConstants().margin8),
child: Stack(
children: <Widget>[
ClipRRect(
borderRadius: BorderRadius.circular(NewsAppConstants().margin8),
child: Container(
child: Text(
title,
style: TextStyle(
color: Colors.white,
fontSize: NewsAppConstants().font16,
fontWeight: FontWeight.w500),
),
alignment: Alignment.center,
width: NewsAppConstants().imageWidth120,
height: NewsAppConstants().imageHeight60,
decoration: BoxDecoration(
borderRadius:
BorderRadius.circular(NewsAppConstants().margin8),
color: Colors.black,
),
),
)
],
),
),
);
}
}
from what I see
you might try one of the below solutions :
try in bloc builder to return Container and inside it handle you states like this :
builder: (context, state) {
return Container(
child: Column(
children: [
if (state is Loading)
CircularProgressIndicator(),
if (state is Loaded)
CatsListView(data: state.data),
try to cover all your kind of states in if/else in your Bloc builder
so let's assume that you have 2 states (state1, state2) so your Bloc builder
would be something like this
builder: (context, state) {
if (state is state1) return Container();
else if (state is state2) return Container();
else return Container();
note that if you covered all states you don't have to do the last else
I created two checkboxes but after clicking on one of them both are marked, as in the picture below, could someone help me solve this problem?
only one can be marked,
my code:
class _LanguageSelectorState extends State<LanguageSelector> {
static final List<String> languagesList = application.supportedLanguages;
static final List<String> languageCodesList =
application.supportedLanguagesCodes;
final Map<dynamic, dynamic> languagesMap = {
languagesList[0]: languageCodesList[0],
languagesList[1]: languageCodesList[1],
};
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
backgroundColor: Colors.white,
iconTheme: IconThemeData(color: Colors.black),
title: Text(AppTranslations.of(context).text("settings_language"), style: TextStyle(color: Colors.black, letterSpacing: 1)),
elevation: 0.0,
centerTitle: true,
bottom: PreferredSize(child: Container(color: Colors.black, height: 0.1), preferredSize: Size.fromHeight(0.1),),
),
body: _buildLanguagesList()
);
}
String selectedLanguage = '';
_buildLanguagesList() {
return ListView.builder(
itemCount: languagesList.length,
itemBuilder: (context, index) {
return _buildLanguageItem(languagesList[index]);
},
);
}
bool _value = false;
_buildLanguageItem(String language) {
return CheckboxListTile(
title: Text(language),
value: _value,
onChanged: (value) {
setState(() {
_value = value;
application.onLocaleChanged(Locale(languagesMap[language]));
});
},
controlAffinity: ListTileControlAffinity.trailing,
);
}
}
thanks for any help :)
////////////////////////////////////////////////////////////////////
Take a look at this example.. Hope that will answer your question how to use checkboxes in listView
List<Map<String, dynamic>> languagesList = [
{'value': false},
{'value': false}
];
ListView.builder(
itemCount: languagesList.length,
itemBuilder: (context, index) {
return CheckboxListTile(
title: Text(languagesList[index]['value'].toString()),
value: languagesList[index]['value'],
onChanged: (value) {
setState(() {
languagesList[index]['value'] = value;
});
},
controlAffinity: ListTileControlAffinity.trailing,
);
}),
The reason your approach didn't work was because you have assigned one variable to all your checkboxes so no wander your checkboxes were updated together
Because all the widgets created by the ListView has the same value _value, if one of the widget gets checked, the value for all of the widgets change as the all depend on the same variable.
Here is a demonstration of how you can do it. it may contain errors.
import 'package:flutter/material.dart';
class LanguageItem extends StatefulWidget {
Key key;
bool isSelected = false;
YOURCLASS application;
String language;
LanguageItem({#required language, #required this.application, this.key
}):super(key:key);
#override
_LanguageItemState createState() => _LanguageItemState();
}
class _LanguageItemState extends State<LanguageItem> {
#override
Widget build(BuildContext context) {
return CheckboxListTile(
title: Text(widget.language),
value: widget.isSelected,
onChanged: (value) {
setState(() {
widget.isSelected = value;
widget.application.onLocaleChanged(Locale(languagesMap[language]));
});
},
controlAffinity: ListTileControlAffinity.trailing,
);
}
}
I am trying to create a list of ids & names from checkbox selection and i want to pass that array list in navigator.pop but somehow i am not able to do it.?
I tried to create model and put that as in list object which can give me my selected values in list or array but I am getting it with last extra (comma(,))
My checkbox and code to create list and pass it into navigator.pop.
THIS IS MAIN PAGE WHERE I WANT TO GET AND REDIRECT TO SECOND LIST PAGE.
var tempRoomFace;
getRoomFaceData() async {
tempRoomFace = await Navigator.push(
context, MaterialPageRoute(builder: (context) => RoomFacilities()));
for (int i = 0; i < areaDataResult.length; i++) {
_selectedroom = _selectedroom + tempRoomFace[i].name + ", ";
_selectedroomID = _selectedroomID + tempRoomFace[i].id + ", ";
print("selected rooms : $_selectedroomID");
}
}
THIS IS LIST PAGE... TO CREATE LIST
------------------ code for passing value ---------------------
List<RoomFacilityModel.Message> tempRoomData =
List<RoomFacilityModel.Message>();
//
List<RoomFacilityModel.Message> roomListOBJ =
List<RoomFacilityModel.Message>();
onPressed: () {
Navigator.pop(context, tempRoomData);
for (int i = 0; i < tempRoomData.length; i++) {
print(
"City List : ${tempRoomData[i].fcid} + ${tempRoomData[i].name} ");
}
},
------------------- Code for checkbox ------------------
Container(
child: CheckboxListTile(
title: Row(
children: <Widget>[
Container(
padding: EdgeInsets.all(5),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(3),
color: Colors.black,
),
height: 26,
width: 26,
child: Image.network(
roomListOBJ[index].icon,
),
),
SizedBox(width: 10),
Text(roomListOBJ[index].name),
],
),
value: roomListOBJ[index].isCheck,
onChanged: (bool value) async {
//
setState(() {
roomListOBJ[index].isCheck = value;
if (value) {
tempRoomData.add(RoomFacilityModel.Message(
fcid: roomListOBJ[index].fcid,
name: roomListOBJ[index].name));
} else {
tempRoomData.removeAt(index);
}
});
}),
);
I want to get resulat on my main page as
tempRoomFace = [TV, Safe box, curtains, iron]
tempRoomFaceID = [3,4,8,1]
You could modify this as much as you want.
class MultiCheckBoxField extends StatelessWidget {
const MultiCheckBoxField({
Key key,
this.count = 1,
this.onSaved,
}) : super(key: key);
final int count;
final FormFieldSetter<List<bool>> onSaved;
#override
Widget build(BuildContext context) {
return FormField<List<bool>>(
initialValue: List.filled(count, false),
onSaved: onSaved,
builder: (FormFieldState field) {
return Column(
mainAxisSize: MainAxisSize.min,
children: List.generate(
count,
(int index) {
return Checkbox(
onChanged: (bool value) {
field.value[index] = value;
field.didChange(field.value);
},
value: field.value[index],
);
},
),
);
},
);
}
}
I'm building an app for training in Flutter and I'm actually stuck in the filter functionality.
I have a ListView where I fetch data from TheMovieDB API and a ModalBottomSheet with three FilterChips for selecting the filter criteria (popular, top rated and latest movies).
And here's where I'm stuck. I want to call the "_loadNextPage()" method when the user presses the "Done" button in the ModalBottomSheet through "performUpdate()" but I can't do it because they're not in the same class.
I'll post the code down below for better understanding.
class _HomePageState extends State<HomePage> {
RequestProvider _requestProvider = new RequestProvider();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("FluttieDB"),
actions: <Widget>[
IconButton(
icon: Icon(Icons.filter_list),
onPressed: () => buildFilterBottomSheet(),
)
],
),
body: MovieList(_requestProvider, _currentFilter),
);
}
void buildFilterBottomSheet() {
showModalBottomSheet(
context: context,
builder: (builder) {
return Container(
height: 150.0,
decoration: BoxDecoration(color: Colors.white),
child: Column(
children: <Widget>[
buildFilterTitle(context),
Expanded(
child: _FilterChipRow(),
),
],
),
);
});
}
Widget buildFilterTitle(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 6.0),
alignment: Alignment.centerLeft,
height: 46.0,
decoration: BoxDecoration(color: Colors.blue),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Text(
"Filter by",
style: TextStyle(color: Colors.white, fontSize: 20.0),
),
OutlineButton(
onPressed: () => performUpdate(context),
padding: const EdgeInsets.all(0.0),
shape: const StadiumBorder(),
child: Text(
"Done",
style: TextStyle(color: Colors.white),
),
),
],
),
);
}
void performUpdate(BuildContext context) {
MovieList _movieList = new MovieList(_requestProvider, _currentFilter);
_movieList.createState()._loadNextPage();
Navigator.pop(context);
}
}
class MovieList extends StatefulWidget {
MovieList(this.provider, this.currentFilter, {Key key}) : super(key: key);
final RequestProvider provider;
final String currentFilter;
#override
_MovieListState createState() => new _MovieListState();
}
class _MovieListState extends State<MovieList> {
List<Movie> _movies = List();
int _pageNumber = 1;
LoadingState _loadingState = LoadingState.LOADING;
bool _isLoading = false;
_loadNextPage() async {
_isLoading = true;
try {
var nextMovies = await widget.provider
.provideMedia(widget.currentFilter, page: _pageNumber);
setState(() {
_loadingState = LoadingState.DONE;
_movies.addAll(nextMovies);
_isLoading = false;
_pageNumber++;
});
} catch (e) {
_isLoading = false;
if (_loadingState == LoadingState.LOADING) {
setState(() => _loadingState = LoadingState.ERROR);
}
}
}
#override
void initState() {
super.initState();
_loadNextPage();
}
#override
Widget build(BuildContext context) {
switch (_loadingState) {
case LoadingState.DONE:
return ListView.builder(
itemCount: _movies.length,
itemBuilder: (BuildContext context, int index) {
if (!_isLoading && index > (_movies.length * 0.7)) {
_loadNextPage();
}
return MovieListItem(_movies[index]);
});
case LoadingState.ERROR:
return Center(
child: Text("Error retrieving movies, check your connection"));
case LoadingState.LOADING:
return Center(child: CircularProgressIndicator());
default:
return Container();
}
}
}
As you can see, I did some experiments in the performUpdate() but it doesn't refresh the ListView with the selected option in the filters and I don't think it's the best way to achieve what I want.
Thanks and sorry if the question is a bit dumb. I'm a little bit newbie in Flutter.
Redux is a great state management library that originated with React and JS, but has been ported to Dart, and has a flutter specific library as well. Redux is a very powerful framework which uses a pub/sub system to allow your view to subscribe to changes to the model, while using a system of "actions" and "reducers" to update the model.
A great tutorial for getting up and running with Redux in Flutter can be found here
Alternatively you could look into the scoped model, which is another state management library for flutter. The scoped model is less capable, but for simple use cases may be more than adequate.
Further reading:
Understand and choose a state management solution
You Might Not Need Redux