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
Related
I am new to flutter (I am an android developer) and I have a problem that I do not have the solution to.
## What i'm looking to do ##
I want to create a horizontal list view with stateful widgets as an item. These items can be selected or deselected. They are part of a page.
[list view item selected][1]
[list view item not selected][2]
The error
Error: Could not find the correct Provider above this EquipmentItemWidget$ Widget
This happens because you used a BuildContext that does not include the provider
of your choice. There are a few common scenarios:
You added a new provider in your main.dart and performed a hot-reload.
To fix, perform a hot-restart.
The provider you are trying to read is in a different route.
Providers are "scoped". So if you insert of provider inside a route, then
other routes will not be able to access that provider.
You used a BuildContext that is an ancestor of the provider you are trying to read.
Make sure that EquipmentItemWidget$ is under your MultiProvider/Provider.
This usually happens when you are creating a provider and trying to read it immediately.
For example, instead of:
Widget build(BuildContext context) {
return Provider<Example>(
create: (_) => Example(),
// Will throw a ProviderNotFoundError, because `context` is associated
// to the widget that is the parent of `Provider<Example>`
child: Text(context.watch<Example>()),
),
}
consider using builder like so:
Widget build(BuildContext context) {
return Provider<Example>(
create: (_) => Example(),
// we use `builder` to obtain a new `BuildContext` that has access to the provider
builder: (context) {
// No longer throws
return Text(context.watch<Example>()),
}
),
}
My code
SearchGamePage.dart
class SearchGamePage extends StatefulWidget {
#override
_SearchGamePageState createState() => _SearchGamePageState();
}
class _SearchGamePageState extends State<SearchGamePage> {
...
#override
Widget build(BuildContext context) {
...
return Scaffold(
body: Container(
margin: EdgeInsets.all(16),
child: SingleChildScrollView(
child: Column(
children: [
buttonSearch,
inputBarSearch,
sliderNumberPerson,
listEquipment,
sliderHardness
],
),
),
));
}
}
EquipChangeNotifier.dart
class EquipChangeNotifier with ChangeNotifier {
bool isEquip;
EquipChangeNotifier(this.isEquip);
//GETTER
bool get isEquipGet => isEquip;
//SETTER
set isEquipSet(bool isEquip){
this.isEquip = isEquip;
notifyListeners();
}
}
EquipmentItemWidget.dart
class EquipmentItemWidget extends StatefulWidget {
final String type;
EquipmentItemWidget({Key key, this.type}) : super(key: key);
#override
_EquipmentItemWidgetState createState() => _EquipmentItemWidgetState(type);
}
class _EquipmentItemWidgetState extends State<EquipmentItemWidget> {
bool isSelected = false;
final type;
final List<String> urlIconEquipment = ['images/equipment/balle_ping_pong.PNG', 'images/equipment/carte.PNG', 'images/equipment/des.PNG', 'images/equipment/goblet.PNG', 'images/equipment/table.PNG'];
_EquipmentItemWidgetState(this.type);
String getPath(String type){
switch(type){
case 'balle':
return urlIconEquipment[0];
case 'carte':
return urlIconEquipment[1];
case 'des':
return urlIconEquipment[2];
case 'goblet':
return urlIconEquipment[3];
case 'table':
return urlIconEquipment[4];
}
}
void _toggleEquipmentSearch(EquipChangeNotifier notifier){
setState(() {
isSelected = !isSelected;
notifier.isEquipSet = isSelected;
});
}
#override
Widget build(BuildContext context) {
EquipChangeNotifier notifier = Provider.of<EquipChangeNotifier>(context);
isSelected = notifier.isEquipGet;
return GestureDetector(
onTap: (){
_toggleEquipmentSearch(notifier);
},
child: Container(
padding: EdgeInsets.all(16),
child: Column(
children: [
ClipRRect(
borderRadius: BorderRadius.circular(300),
child: Container(
padding: EdgeInsets.all(5),
color: Colors.white24,
child: (Image.asset(
(!isSelected?getPath(type):'images/equipment/valid.PNG'),
width: 50,
height: 50,
fit: BoxFit.scaleDown,
)),
)),
Container(
margin: EdgeInsets.only(top: 8),
child: Text(type, style: Theme.of(context).textTheme.headline6,))
],
),
),
);
}
}
Thank you in advance for your help !!
I am developing an app for a community with login, signup, meetings and chats in flutter. After login successful, the route goes to Home which has five bottom navigation. I am using Firebase in this app for Authentication and Firestore.
I would like to fetch the data once when Home Component started and pass the data to other five bottom navigation bar components.
Now I am fetching the data whenever I switched between navigation components. This increase the Firestore reads.
I tried passing the data through components using constructor variables. But this doesn't work. It shows error that data can't be passed to bottom navigation components Here is my code.
Home.dart
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
User currentUser;
String userId;
Home({this.currentUser, this.userId});
}
class _HomeState extends State<Home> {
CurrentUser userInfo;
DocumentSnapshot doc;
int _selectedIndex = 0;
List<String> upcoming_seven_days;
FirestoreService _firestoreService = FirestoreService();
static const TextStyle optionStyle =
TextStyle(fontSize: 30, fontWeight: FontWeight.bold);
static List<Widget> _widgetOptions = <Widget>[
Dashboard(),
MeetingList(),
EventList(),
Chat(),
Profile(),
];
static const List<Widget> _appBarText = <Widget>[
Text(
'Dashboard',
style: TextStyle(
fontWeight: FontWeight.w300,
fontSize: 26,
),
),
Text(
'Meetings',
style: TextStyle(
fontWeight: FontWeight.w300,
fontSize: 26,
),
),
Text(
'Events',
style: TextStyle(fontWeight: FontWeight.w300, fontSize: 26),
),
Text(
'Chat',
style: TextStyle(fontWeight: FontWeight.w300, fontSize: 26),
),
Text(
'Profile',
style: TextStyle(fontWeight: FontWeight.w300, fontSize: 26),
),
];
#override
void initState() {
// TODO: implement initState
super.initState();
//setCurrentUserID(widget.currentUser.uid);
//setCurrentUserData(doc.data());
}
void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: _appBarText.elementAt(_selectedIndex)),
body: Container(
padding: EdgeInsets.symmetric(horizontal: 10),
width: double.maxFinite,
child: _widgetOptions.elementAt(_selectedIndex),
),
bottomNavigationBar: BottomNavigationBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.dashboard),
title: Text('Dashboard'),
backgroundColor: Colors.black),
BottomNavigationBarItem(
icon: Icon(Icons.people),
title: Text('Meetings'),
backgroundColor: Colors.black),
BottomNavigationBarItem(
icon: Icon(Icons.calendar_view_day),
title: Text('Events'),
backgroundColor: Colors.black),
BottomNavigationBarItem(
icon: Icon(Icons.chat),
title: Text('Chat'),
backgroundColor: Colors.black),
BottomNavigationBarItem(
icon: Icon(Icons.person),
title: Text('Profile'),
backgroundColor: Colors.black),
],
currentIndex: _selectedIndex,
selectedItemColor: Colors.lightBlue[200],
onTap: _onItemTapped,
elevation: 8.0,
backgroundColor: Colors.black,
),
);
}
}
This is where I fetch the data from Firestore whenever the user switch to Meeting list component. I don't want to do like that. Rather, I want to pass the respective data from Home to other components. And it should be snapshot, so it can listen to changes.
MeetingList.dart
class MeetingList extends StatelessWidget {
var userInfo;
FirebaseAuth firebaseAuth = FirebaseAuth.instance;
Future getuserinfo() async {
// final uid = firebaseAuth.currentUser.uid;
// userinfo = await firestoreService.getCurrentUserInfo(uid);
// userinfo = userinfo.data().length;
// //print(userinfo);
// return uid;
final uid = firebaseAuth.currentUser.uid;
DocumentSnapshot user = await FirebaseFirestore.instance
.collection('userProfiles')
.doc(uid)
.get();
userInfo = user.data();
return userInfo;
}
#override
Widget build(BuildContext context) {
CollectionReference meetings =
FirebaseFirestore.instance.collection('meetings');
return FutureBuilder(
future: getuserinfo(),
builder: (context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return LoadingIndicator();
} else {
return StreamBuilder<QuerySnapshot>(
stream: meetings.snapshots(),
builder:
(BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) {
return Text('Something went wrong');
}
if (snapshot.connectionState == ConnectionState.waiting) {
return LoadingIndicator();
}
return new ListView(
children: snapshot.data.docs.map((DocumentSnapshot document) {
String meetingRole = document.data()['role'];
var userRole = userInfo['role'];
print(userRole);
if (meetingRole == 'all' || meetingRole == userRole) {
return Meeting_Card(
meeting: document.data(),
);
} else {
return Container();
}
}).toList(),
);
},
);
}
},
);
}
}
Your help would be so much helpful for the community.
You can use Provider package for this, which is a wrapper around the InheritedWidget in flutter.
InheritedWidget is used to efficiently propagate information down
the tree, without having to pass them through various constructors
down the widget tree.
You can find more information about InheritedWidget here.
Provider package is wrapper around InheritedWidget to make them
easier to use and more reusable.
More information on Provider in the documentation here
To implement your solution using Provider:
Create a ChangeNotifier class called UserProvider to hold the data you want common between all the children widgets:
class UserProvider extends ChangeNotifier {
User userInfo;
Future getuserinfo() async {
// final uid = firebaseAuth.currentUser.uid;
// userinfo = await firestoreService.getCurrentUserInfo(uid);
// userinfo = userinfo.data().length;
// //print(userinfo);
// return uid;
final uid = firebaseAuth.currentUser.uid;
DocumentSnapshot user = await FirebaseFirestore.instance
.collection('userProfiles')
.doc(uid)
.get();
userInfo = user.data();
return userInfo;
}
}
Now wrap your Home Widget in a ChangeNotifierProvider widget:
class HomeScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider<UserProvider>(
lazy: false,
create: (context) => UserProvider(),
child: Home(),
);
}
}
Now you can access the content of the UserProvider class from wherever down the same widget tree (Any of the tabs) by using:
/// Get an instance of the UserProvider in the ancestors of the current widget tree like this.
UserProvider userProvider = Provider.of<UserProvider>(context);
/// Call any method inside the UserProvider class like this
userProvider.getUserInfo();
/// access any data variables inside the UserProvider class like this.
User userInfo = userProvider.userInfo;
You can also take a look at the Consumer and Selector widgets in the provider package, which provide an efficient ways to redraw the UI based on certain parameters of the ChangeNotifier class, when the notifyListeners() methid is called from the ChangeNotifier class.
As my first Flutter project I'm building an app for a newspaper. There are a number of news categories. For those categories, I have created a generic Widget, in the home screen that Widget will be shown multiple times for multiple categories in a Listview, those category Widgets have Listview in them too as I bring multiple news from those categories. The Widget class that has this generic Widget is Newsfeed.dart which will be given below.
This generic Widget is called from a another Widget class WidgetFactory.dart which actually calls API and builds the home screen by using the above mentioned generic Widget for categories. This class uses a Listview.builder which is inside FutureBuilder.
The problem is, when I open the app in the screen the news appears but I can't scroll, it stays fixed. I have checked if the API is actually bringing the news, in console I have printed the API response, all of the news are fetched but still I can't scroll.
the flow is main.dart -> WidgetFactory() -> Newsfeed()
WidgetFactory()
class WidgetFactory extends StatefulWidget {
#override
_WidgetFactoryState createState() => _WidgetFactoryState();
}
class _WidgetFactoryState extends State<WidgetFactory> {
List homeScreenCategories = [4, 14, 13, 23, 8015, 22];
Future<List> newsPostList;
List<List<NewsPost>> categoryNewsPostList;
#override
void initState() {
super.initState();
newsPostList = fetchNews();
}
#override
Widget build(BuildContext context) {
SizeConfig().init(context);
return Container(
alignment: Alignment.center,
child: Container(
child: RefreshIndicator(
child: FutureBuilder(
future: newsPostList,
builder: (BuildContext context, AsyncSnapshot snapshot) {
if(snapshot.data == null) {
return Container(
child: CircularProgressIndicator()
);
} else {
return ListView.builder(
shrinkWrap: true,
physics: const AlwaysScrollableScrollPhysics(),
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) {
return _getCategoryNews(snapshot, index);
},
);
}
},
),
onRefresh: () {
fetchNews();
}
),
),
);
}
Future<List> fetchNews() async {
String url = "url";
Response response = await Dio().get(url);
if(response.statusCode == 200) {
List newsPostList = [];
for(int i=0; i<response.data.length; i++) {
newsPostList.add(response.data[i]);
}
return newsPostList;
} else {
throw Exception("Failed to fetch category");
}
}
Widget _getCategoryNews(snapshot, int index) {
List<NewsPost> newsPostList = [];
for(var c in snapshot.data[index]['items']) {
NewsPost newsPost = NewsPost.getNewsPostFromAPI(c);
newsPostList.add(newsPost);
}
return Newsfeed(newsPostList, "National");
}
}
Newsfeed()
class Newsfeed extends StatefulWidget {
String categoryName;
List<NewsPost> newsPostList;
Newsfeed(this.newsPostList, this.categoryName);
#override
_NewsfeedState createState() => _NewsfeedState(this.newsPostList, this.categoryName);
}
class _NewsfeedState extends State<Newsfeed> {
final GlobalKey<ScaffoldState> _scaffoldKeyTwo = new GlobalKey<ScaffoldState>(debugLabel: '_MainScreenKey');
String categoryName;
_NewsfeedState(this.newsPostList, this.categoryName);
List<NewsPost> newsPostList;
var dio = new Dio();
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
SizeConfig().init(context);
return Container(
alignment: Alignment.center,
child: ListView.builder(
shrinkWrap: true,
itemCount: newsPostList.length,
itemBuilder: (BuildContext context, int index) {
print(newsPostList[index]);
return _getNewsPostWidgets(index);
}
),
);
}
Widget _getNewsPostWidgets(int index) {
var newsPost = newsPostList[index];
if(index < 5) {
if(index == 0) {
return GestureDetector(
onTap: () {
Navigator.push(
context,
ScaleTransitionRoute(
page: NewsPostDetails(newsPostList, index)
)
);
},
child: Column(
children: <Widget>[
Container(
padding: EdgeInsets.fromLTRB(10, 0, 0, 0),
//constraints: BoxConstraints(minWidth: double.infinity, maxWidth: double.infinity),
constraints: BoxConstraints.expand(
width: double.infinity,
height: 40
),
color: const Color(0xFF2b4849),
child: Text(
this.categoryName,
style: TextStyle(
fontSize: 33,
color: Colors.white
),
),
),
BlockHeadline(newsPost)
],
)
);
}
else {
return GestureDetector(
onTap: () {
Navigator.push(
context,
ScaleTransitionRoute(
page: NewsPostDetails(newsPostList, index)
)
);
},
child: ListedNews(newsPost),
);
}
}
else {
return Container(
color: const Color(0xFF2b4849),
child: index == 5 ? FlatButton(
child: Text(
"See More",
style: TextStyle(
color: Colors.white
),
),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (BuildContext context) => NewsFeedForSpecificCategory(newsPostList)
)
);
},
) : Container(),
);
}
}
openNewsPostDetails(List<NewsPost> newsPostList, int index) {
Navigator.push(
context,
ScaleTransitionRoute(
page: NewsPostDetails(newsPostList, index)
)
);
}
}
What I have tried
I found some questions that relates to this problem a bit. Tried those.
I used shrinkwrap=true in my Listview.builder, but of no use.
Tried using Column inside SingleChildScrollView(), still did not work.
Inside the Listview.builder added physics as AlwaysScrollable(), also in vain.
As I'm new to flutter what I tried might seem dumb.
One more thing is, the news that shows up in the home screen, that takes me to the details page fine and there swiping left right takes me to other news also. It's the home screen that is causing trouble, not scrolling.
It would be great help if you could kindly give some clues.
I have found the solution to this problem. It was quite simple actually.
In the build() method of my _NewsfeedState class I have added ClampingScrollPhysics() as physics.
#override
Widget build(BuildContext context) {
SizeConfig().init(context);
return Container(
alignment: Alignment.center,
child: ListView.builder(
shrinkWrap: true,
physics: ClampingScrollPhysics(),
itemCount: newsPostList.length,
itemBuilder: (BuildContext context, int index) {
print(newsPostList[index]);
return _getNewsPostWidgets(index);
}
),
);
}
It worked.
Putting ListView inside ListView is an anti pattern.
There are several solutions for this problem,
Solution 1:
Merge those two list of items into a single list of items and
display it as a single ListView.
Solution 2:
You can use SliverList for your use case. SliverList can wrap multiple SliverLists inside a single CustomScrollView.
This will help you.
child: ListView.builder(
physics: NeverScrollableScrollPhysics(),
Thanks.
Im very new to flutter and dart so this might be a basic question. However, what I would like to know is how to implement a swipe to delete method in a listview to delete data from firestore too.
I tried using the Dissmissible function but i dont understand how to display the list and I cant seem to understand how to remove the selected data as well.
This here is my dart code
Widget build(BuildContext context) {
return new Scaffold(
resizeToAvoidBottomPadding: false,
appBar: new AppBar(
centerTitle: true,
automaticallyImplyLeading: false,
title: Row(mainAxisAlignment: MainAxisAlignment.spaceBetween,children:
<Widget>[
Text("INVENTORY",textAlign: TextAlign.center,) ,new IconButton(
icon: Icon(
Icons.home,
color: Colors.black,
),
onPressed: () {
Navigator.push(
context,
SlideLeftRoute(widget: MyHomePage()),
);
})]),
),body: ListPage(),
);
}
}
class ListPage extends StatefulWidget {
#override
_ListPageState createState() => _ListPageState();
}
class _ListPageState extends State<ListPage> {
Future getPosts() async{
var firestore = Firestore.instance;
QuerySnapshot gn = await
firestore.collection("Inventory").orderBy("Name",descending:
false).getDocuments();
return gn.documents;
}
#override
Widget build(BuildContext context) {
return Container(
child: FutureBuilder(
future: getPosts(),
builder: (_, snapshot){
if(snapshot.connectionState == ConnectionState.waiting){
return Center(
child: Text("Loading"),
);
}else{
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder:(_, index){
return EachList(snapshot.data[index].data["Name"].toString(),
snapshot.data[index].data["Quantity"]);
});
}
}),
);
}
}
class EachList extends StatelessWidget{
final String details;
final String name;
EachList(this.name, this.details);
#override
Widget build(BuildContext context) {
// TODO: implement build
return new Card(
child:new Container(
padding: EdgeInsets.all(8.0),
child: new Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
new Row(
children: <Widget>[
new CircleAvatar(child: new Text(name[0].toUpperCase()),),
new Padding(padding: EdgeInsets.all(10.0)),
new Text(name, style: TextStyle(fontSize: 20.0),),
],
),
new Text(details, style: TextStyle(fontSize: 20.0))
],
),
),
);
}
}
You should use Dismissible widget. I used it for an inbox list retrieved from Firestore. Inside your EachList return something like this
return Dismissible(
direction: DismissDirection.startToEnd,
resizeDuration: Duration(milliseconds: 200),
key: ObjectKey(snapshot.documents.elementAt(index)),
onDismissed: (direction) {
// TODO: implement your delete function and check direction if needed
_deleteMessage(index);
},
background: Container(
padding: EdgeInsets.only(left: 28.0),
alignment: AlignmentDirectional.centerStart,
color: Colors.red,
child: Icon(Icons.delete_forever, color: Colors.white,),
),
// secondaryBackground: ...,
child: ...,
);
});
IMPORTANT: in order to remove the list item you'll need to remove the item from the snapshot list as well, not only from firestore:
_deleteMessage(index){
// TODO: here remove from Firestore, then update your local snapshot list
setState(() {
snapshot.documents.removeAt(index);
});
}
Here the doc: Implement Swipe to Dismiss
And here a video by Flutter team: Widget of the week - Dismissilbe
You can use the flutter_slidable package to achieve the same.
You can also check out my Cricket Team on Github in which I have did the same you want to achieve, using same package.
Example for how to use package are written here.
I'd like to add that when deleting a document from Firestore, no await is needed as the plugin automatically caches the changes and then syncs them up when there is a connection again.
For instance, I used to use this method
Future deleteWatchlistDocument(NotifierModel notifier) async {
final String uid = await _grabUID();
final String notifierID = notifier.documentID;
return await _returnState(users.document(uid).collection(watchlist).document(notifierID).delete());
}
in which I was waiting for the call to go through, however this prevented any other call to go through and only allowed one. Removing this await tag however solved my issue.
Now I can delete documents offline, and the changes will sync up with Firestore when a connection is regained. It's pretty cool to watch in the console.
I'd recommend watching this video about offline use with Firestore
I'm new to Flutter and need a bit of help. I've built a random joke generator app that reads data from the API and displays a new joke every time a button is pressed. I want to make the app show the previously fetched data when device is offline. I tried searching online but found nothing that does it using Flutter.
class _HomePageState extends State<HomePage> {
List data;
Future<Jokes> post;
String url="https://official-joke-api.appspot.com/random_joke";
var response;
Future<Jokes> getData() async {
response =
await http.get(url, headers: {"Accept": "application/json"});
if (response.statusCode == 200) {
return Jokes.fromJson(json.decode(response.body));
} else {
throw Exception('Failed to load post');
}
}
changeApi()
{
setState(() {
if (response.statusCode == 200) {
return Jokes.fromJson(json.decode(response.body));
} else {
throw Exception('Failed to load post');
}
});
}
#override
void initState()
{
super.initState();
this.getData();
}
#override
Widget build(BuildContext context) {
final key = new GlobalKey<ScaffoldState>();
// TODO: implement build
return Scaffold(
key: key,
backgroundColor: Colors.amberAccent,
body: new Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new FutureBuilder<Jokes>(
future:
getData(),
builder: (context, snapshot) {
if (snapshot.hasData) {
//checks if the response returns valid data
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new GestureDetector(
child: new Text(
snapshot.data.setup ,
style: TextStyle(fontFamily: "Rock Salt"),
),
onLongPress: ()
{
Clipboard.setData(new ClipboardData(text: snapshot.data.setup, ));
key.currentState.showSnackBar(
new SnackBar(content: new Text("Copied to Clipboard"),));
},
),
/
SizedBox(
height: 10.0,
),
new GestureDetector(
child: new Text(
" - ${snapshot.data.punchline}",
style: TextStyle(fontFamily: "Roboto"),
),
onLongPress: ()
{
Clipboard.setData(new ClipboardData(text: snapshot.data.punchline));
key.currentState.showSnackBar(
new SnackBar(content: new Text("Copied to Clipboard"),));
},
),
],
),
);
} else if (snapshot.hasError) {
//checks if the response throws an error
return Text("${snapshot.error}");
}
return CircularProgressIndicator();
},
),
SizedBox(
height: 25.0,
),
new RaisedButton(
onPressed: changeApi,
color: Colors.pinkAccent,
child: Text("Press for a new joke", style: TextStyle(color: Colors.white,)),
)
],
),
),
);
}
}
class Jokes {
final String setup;
final String punchline;
Jokes({this.setup, this.punchline});
factory Jokes.fromJson(Map<String, dynamic> json) {
return Jokes(setup: json['setup'], punchline: json['punchline']);
}
}
Api
Here's my full code: code
There are some videos about caching, here's the one from flutter team, and one from tensor programming channel.
You can use connectivity plugin to check whether the device is offline.
If device is offline, show data from shared_preferences or sqflite, if it's online, fetch new data (and of course update your cache).