Automatic update of list data upon change - flutter

I have built an app in Flutter that shows a ListView of data pulled from google sheets. What I would like to do is have the list automatically refresh itself when data is changed on google sheets. I'm not sure if it is possible, but any help would be appreciated.
class BodFullList extends StatefulWidget {
#override
_BodFullListState createState() => _BodFullListState();
}
class _BodFullListState extends State<BodFullList> {
final StreamController _streamController = StreamController();
List<DGL> dgl = [];
int index = 0;
#override
void initState() {
super.initState();
getDGL();
}
Future getDGL({int? index}) async {
final dgl = await BodSheetsApi.getAll();
setState(() {
this.dgl = dgl;
});
}
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(
title: const Text(MyApp.title),
centerTitle: true,
),
body:RefreshIndicator(
onRefresh: getDGL,
child: ListView.builder(
itemCount: dgl.length,
itemBuilder: (context, index){
return Card (
child: ListTile(
onTap: () {
Navigator.push(
context, MaterialPageRoute(
builder: (context) => BodEntryEdit(),
settings: RouteSettings(
arguments: dgl[index],
)
)
);
},
title: Text(dgl[index].loc)
),
);
},
),
)
);
}

actually the informations you have provided are not enough. But, as i imagine, you are fetching the data once and not listening. To listen to changes, you need to use websockets. I remember that GoogleSheet allows to use webhooks from there, you can implement whatever you want.

Related

Problem with Future<dynamic> is not a subtype of type List<Routes> in Flutter

I have problem with async-await. (I am not very good at programming, but learning by creating random apps...)
Problem: Using dio to get data from Node.js json-server, but I cant transform data from
Future to List. Error: type 'Future' is not a subtype of type 'List' at line 13. List<Routes> routes = _getData();
I have read a lot of discussions here on stackoverflow and many other websites, but I just can't make it work. :( So here I am asking with specific code.
Needed code:
Code where error appears (route_list_screen.dart)
import 'package:app/api/api.dart';
import 'package:flutter/material.dart';
import 'package:app/models/routes.dart';
class RouteList extends StatefulWidget {
const RouteList({Key? key}) : super(key: key);
#override
State<RouteList> createState() => _RouteListState();
}
List<Routes> routes = _getData();
class _RouteListState extends State<RouteList> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Text'),
automaticallyImplyLeading: true,
centerTitle: true,
),
body: ListView.separated(
itemCount: routes.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(routes[index].number),
subtitle: Text(routes[index].routeType),
trailing: const Text('??/??'),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => RouteSelected(
passedRoutes: routes[index],
),
),
);
},
);
},
separatorBuilder: (context, index) {
return const Divider();
},
),
);
}
}
_getData() async {
Future<dynamic> futureOfRoutes = getRouteList(856);
List<dynamic> routes = await futureOfRoutes;
return routes;
}
Connecting to server (api.dart)
import 'package:app/models/routes.dart';
const _url = 'http://10.0.2.2:3000/routes';
getRouteList(int driverId) async {
Response response;
var dio = Dio(BaseOptions(
responseType: ResponseType.plain,
));
response = await dio.get(_url, queryParameters: {"driver_id": driverId});
final data = routesFromJson(response.data);
return data;
}
List with param Routes = Routes is model from app.quicktype.io
_getData() returns a future, you can't direct assign it on List<Routes> where it is Future<dynamic>.
You can use initState
class _RouteListState extends State<RouteList> {
List<Routes>? routes;
_loadData() async {
routes = await _getData();
setState(() {});
}
#override
void initState() {
super.initState();
_loadData();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: routes == null
? Text("On Future ....")
: ListView.separated(
itemCount: routes?.length??0,
itemBuilder: (context, index) {
return ListTile(
title: Text(routes![index].number),
subtitle: Text(routes![index].routeType),
trailing: const Text('??/??'),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => RouteSelected(
passedRoutes: routes![index],
),
),
);
},
);
},
separatorBuilder: (context, index) {
return const Divider();
},
),
);
}
}
Also check FutureBuilder

Why my Flutter JSON data didn't updated from setState?

I've made the JSON data and appear it into FutureBuilder with ListView.builder widget. I want to create a favorite Icon in the trailing of the ListView.builder. So i created it with IconButton, but when I create setState to make some item as favorited, the data didn't updated.
Here is my code
import 'package:flutter/material.dart';
import 'package:json_test/class/doa.dart';
import 'package:json_test/page/DoaPage.dart';
class MainPage extends StatefulWidget {
#override
_MainPageState createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
Future<List<Doa>> fetchDoa(BuildContext context) async {
final jsonstring =
await DefaultAssetBundle.of(context).loadString('assets/doa.json');
return doaFromJson(jsonstring);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("JSON Data test"),
),
body: Container(
child: FutureBuilder(
future: fetchDoa(context),
builder: (context, snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) {
Doa doa = snapshot.data[index];
return Card(
margin: EdgeInsets.all(8),
child: ListTile(
title: Text(doa.judul),
onTap: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (BuildContext context) =>
DoaPage(
doa: doa,
)));
},
trailing: IconButton(
icon: Icon(
doa.fav
? Icons.favorite
: Icons.favorite_border,
color: doa.fav ? Colors.red : null,
),
onPressed: () =>
setState(() => doa.fav = !doa.fav),
)));
},
);
}
return CircularProgressIndicator();
})));
}
}
and this is the
preview
the thing is that when you call setState you run build again, and that in turn runs the FutureBuilder again with the original Doa object.
you need to keep a variable that will hold the changes in your _MainPageState outside the build method, theres a few ways to do that and in your case its a little more complicated because you need the context in your fetchDoa.
one workaround is creating a doaList variable to hold the fetched data outside the build and changing the fetchDoa function to set the doaList instead of returning it(that's why it's Future now.
but that's not enough because the FutureBuilder will just set the doaList from scrach every time build runs, so we'll add a _isInit bool to check if its the first time running build. after that you should replace all the 'snapshot.data' with doaList as the snapshot holds nothing
class _MainPageState extends State<MainPage> {
List<Doa> doaList;
bool _isInit = true;
Future<void> fetchDoa(BuildContext context) async {
final jsonstring =
await DefaultAssetBundle.of(context).loadString('assets/doa.json');
doaList = doaFromJson(jsonstring);
_isInit = false;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("JSON Data test"),
),
body: Container(
child: FutureBuilder(
future: _isInit? fetchDoa(context): Future(),
builder: (context, _) {
try this and tell me if it works :)

How to use dynamic global list in flutter

I am new to Flutter and attempting sample mutual fund app to cover all basic widgets.
Requirement -> After selecting MF scheme, when user confirms on "buyNow" screen, corresponding scheme should get added to global dynamic list in "Cart" screen. This is basically a cart which is accessible to user on any screen, similar to shopping cart. I want to update cart list on "buyNow" screen and display same on "Cart" screen.
I have followed link to learn about 'provider' method of flutter to solve this, but not able to do.
PFB code
Main.dart
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => CartModel(),
child: MaterialApp(
home: Schemelist(),
routes: {
'/landing': (context) => Landing(),
'/schemelist': (context) => Schemelist(),
'/schemeBuy': (context) => SchemeBuy(),
'/buyNow': (context) => BuyNow(),
'/cart': (context) => Cart(),
},
),
),
);
}
Cartmodel.dart
import 'package:flutter/foundation.dart';
class CartModel with ChangeNotifier{
String schemeName;
String type;
String fromDate;
String toDate;
double amount;
List<CartModel> _cartList=[];
CartModel({this.amount,this.fromDate,this.schemeName,this.toDate,this.type});
void addToCart(CartModel cartObj){
_cartList.add(cartObj);
notifyListeners();
}
double get totalAmount =>
_cartList.fold(0, (total, current) => total + current.amount);
}
BuyNow.dart
RaisedButton(
onPressed: () {
_cart=new CartModel(amount:1000,fromDate:_dateTime.toString(),schemeName:widget.investmentObj.schemeName,toDate:_dateTime1.toString(),type:'SIP');
var cart = Provider.of<CartModel>(context);
cart.addToCart(_cart);
Navigator.pushNamed(context, '/cart');
},
child: Text('Yes'),
),
Cart.dart //where I will display dynamic list
Widget build(BuildContext context) {
var cart = Provider.of<CartModel>(context);
return Scaffold(
appBar: AppBar(
title: Text('Cart'),
centerTitle: true,
),
body: ListView.builder(
itemCount: --not able to access list--
itemBuilder: (context, index) => ListTile(
title: Text(
-------
),
),
),
);
}
First we should modify CartModel class. The fields (such as schemeName) should belong to the CartItem class, and the CartModel should only do its own thing (addToCart and others).
class CartModel with ChangeNotifier {
List<CartItem> _itemList = [];
// An unmodifiable view of the items in the cart.
UnmodifiableListView<CartItem> get itemList => UnmodifiableListView(_itemList);
void addToCart(CartItem item) {
_itemList.add(item);
notifyListeners();
}
double get totalAmount => _itemList.fold(0, (total, current) => total + current.amount);
}
class CartItem{
String schemeName;
String type;
String fromDate;
String toDate;
double amount;
CartItem({this.amount, this.fromDate, this.schemeName, this.toDate, this.type});
}
Then, in Cart.dart
Widget build(BuildContext context) {
var itemList = Provider.of<CartModel>(context).itemList;
return Scaffold(
appBar: AppBar(
title: Text('Cart'),
centerTitle: true,
),
body: ListView.builder(
itemCount: itemList.length,
itemBuilder: (_, index) {
var item = itemList[index];
return Text(item.schemeName);
},
),
);
}
You will get a error while click RaisedButton:
Tried to listen to a value exposed with provider, from outside of the widget tree.
This is likely caused by an event handler (like a button's onPressed) that called
Provider.of without passing `listen: false`.
To fix it, edit BuyNow.dart:
RaisedButton(
onPressed: () {
var _item = CartItem(amount: 1000, fromDate: _dateTime.toString(), schemeName: widget.investmentObj.schemeName, toDate: _dateTime1.toString(), type: 'SIP');
//just set listen to false
var cart = Provider.of<CartModel>(context, listen: false);
cart.addToCart(_item);
Navigator.pushNamed(context, '/cart');
},
child: Text('Yes'),
),

ListView along with http request inside the builder keeps on refreshing infinitely even though itemCount is constant

In the code snippet -
The http request keeps on running forever and the ListView keeps on updating as a result forever too.
1 - To my understanding, shouldn't it run only 5 times as itemCount is given as 5?
2 - What would be a better way to generate different http requests for different items of the list
without storing it in a list beforehand? (kind of like cached images)
////
ListView.separated(
separatorBuilder: (context, int) => Divider(),
itemCount: 5,
itemBuilder: (context, index) {
http.get('http://icanhazdadjoke.com',
headers: {'Accept': 'text/plain'}).then((value) async {
var s =value;
setState(() {
joke = s.body;
});
});
return ListTile(
title: Text(index.toString()),
subtitle: Text(joke),
);
},
),
////
Your api call and your view logic should separated. Http calls may take a while to load and your ListView expects to build initially and only change when the state changes. What you are trying to achieve will work with the following code:
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
List<String> jokes = [];
#override
initState(){
loadJokes();
super.initState();
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Google Fonts'),
),
body: ListView.separated(
separatorBuilder: (context, int) => Divider(),
itemCount: jokes.length,
itemBuilder: (context, index){
return ListTile(
title: Text(index.toString()),
subtitle: Text(jokes[index]),
);
},
)
);
}
void loadJokes() async {
for( var i = 0 ; i <= 5; i++ ) {
http.get('http://icanhazdadjoke.com',
headers: {'Accept': 'text/plain'}).then((value) async {
setState(() {
jokes.add(value.body.toString());
});
});
}
}
}
On first time, when widget builds, it triggers get call.
Every time the get call gets its response, then you are calling setState. Calling set state will rebuild the widget again. Which will result in triggering the get call again.
you can make get call only once by calling it from initState
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
void main() {
runApp(
MaterialApp(
home: MainPage(),
debugShowCheckedModeBanner: false,
),
);
}
class MainPage extends StatefulWidget {
#override
_MainPageState createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
Future<String> joke;
#override
void initState() {
joke = _httpGetRequest();
super.initState();
}
Future<String> _httpGetRequest() async {
http.Response response = await http.get(
'http://icanhazdadjoke.com',
headers: {'Accept': 'text/plain'},
);
return response.body;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Demo")),
body: FutureBuilder(
future: joke,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting)
return Center(
child: CircularProgressIndicator(),
);
else if (snapshot.hasError)
return Center(
child: Text(snapshot.error.toString()),
);
else
return ListView.separated(
separatorBuilder: (context, int) => Divider(),
itemCount: 5,
itemBuilder: (context, index) {
return ListTile(
title: Text(index.toString()),
subtitle: Text(snapshot.data ?? "null"),
);
},
);
},
),
);
}
}

Bloc stream how does it get populated

I have the following home page within my app:
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
final CategoriesBloc _categoriesBloc = BlocProvider.of<CategoriesBloc>(context);
return Scaffold(
appBar: AppBar(
title: Text('E-Commerce'),
centerTitle: true,
actions: <Widget>[CartButton()],
),
body: StreamBuilder(
stream: _categoriesBloc.outCategories,
builder: (BuildContext context, AsyncSnapshot<List<Category>> categories) {
if (categories.hasData) {
return ListView.builder(
itemCount: categories.data.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text(categories.data[index].name,
style: TextStyle(fontSize: 24.0)),
onTap: () => Navigator.of(context).push(MaterialPageRoute(
builder: (BuildContext context) => BlocProvider<ProductsBloc>(
child: SelectedCategoryPage(),
bloc: ProductsBloc(categories.data[index]),
))));
},
);
}
return SizedBox();
},
));
}}
Which utilizes the categories bloc:
class CategoriesBloc implements BlocBase {
List<Category> _categories;
StreamController<List<Category>> _categoriesController =
StreamController<List<Category>>();
Sink<List<Category>> get _inCategories => _categoriesController.sink;
Stream<List<Category>> get outCategories => _categoriesController.stream;
CategoriesBloc() {
getCategories();
}
void getCategories() {
DbApi dbApi = DbApi();
_categories = dbApi.getCategories();
_inCategories.add(_categories);
}
#override
void dispose() {
_categoriesController.close();
}
}
I'm currently following a Udemy course trying to learn Flutter, my question which the instructor hasn't answered is, you see the following line:
stream: _categoriesBloc.outCategories,
This returns all categories with no issue, but how does it access the data? because from the category bloc I only populate _inCategories with data? I've gone through all the code and I can't find anywhere where outCategories gets populated?
Looking for clarification into how this actually works would be great.