Instant update of SQflite db when using Future methods in flutter - flutter

I was working on local storage in flutter using SQflite db and Used Future to extract the file from the db to display using ListTile but it does't update instantly like stream do when I insert a new value to the db.
// This method is from the database to get the tasks that has been entered!
{
Future<List<Model>> getTasks() async {
Database _db = await database();
List<Map<String, dynamic>> taskMap = await _db.query('tasks');
return List.generate(taskMap.length, (index) {
return Model(
id: taskMap[index]['id'],
name: taskMap[index]['name'],
fatherName: taskMap[index]['fatherName']);
});
}
}
```
// This is Future Builder to extract the data from the database
{
Expanded(
child: FutureBuilder(
future: _dbHelper.getTasks(),
builder: (context, snapshot) {
return ListView.builder(
itemCount: 3,
itemBuilder: (context, index) {
return ContactList(snapshot.data[index], index);
},
);
},
),
)
}
```

// This is the answer
{
// this is method in provider class to get the task inserted in the db
Future loadTaskList() async {
_isLoading = true;
notifyListeners();
_taskList = await db.getTasks();
_isLoading = false;
notifyListeners();
}
}
// call the provider class in the main.dart file like this
{
ChangeNotifierProvider(
create: (ctx) => InputData()..loadTaskList(),
),
}
// then just use Consumer or Provider when you access the methods.
**This work perfectly for me!**

Related

FutureBuilder update by timer

I don't quite understand how you can update future in FutureBuilder by timer. I tried to create a timer and give it to the future, but it didn't work out and there was an error: type '_Timer' is not a subtype of the 'Future?'
my handler with a request:
Future<ObjectStateInfoModel> read(int id) async {
TransportResponse response = await transport.request(
'get',
RequestConfig(path: path + '($id)'),
TransportConfig(
headers: {},
));
ObjectStateInfoModel objectState = ObjectStateInfoModel.fromJson(response.data);
return objectState;
}
my FutureBuilder:
return FutureBuilder<ObjectStateInfoModel>(
future: logexpertClient.objectsState.read(object.id),
builder: (context, snapshot) {
if (snapshot.hasData) {
final data = snapshot.data!;
on the advice of one of the commentators i converted FutureBuilder to StreamBuilder and created such a stream and then everything works correctly:
stream = Stream.periodic(const Duration(seconds: 5)).asyncMap((_) async {
return logexpertClient.objectsState.read(object.id);
});
Use refreshable_widget, which is built specifically for this.
https://pub.dev/packages/refreshable_widget
Flexible(
child: RefreshableWidget<num>(
initialValue: challenge.userParticipation!.donePercent,
refreshCall: () async {
final challenge =
await cadoo.getChallengeDetail(
id: widget.challengeId,
);
return challenge.userParticipation!.donePercent;
},
builder: (context, value) {
return DonePercentWidget(
percent: value,
);
},
),
),
Pass a refresh call and how often you want to refresh, widget will build whatever on builder method.

Flutter: StreamBuilder with Firebase

I have a ListView of objects from Firebase in which I would like to have it refresh using a StreamBuilder when the data changes.
I can load up my list fine & when data changes my list does refresh.
The issue I am having is instead of the ListTile that has the change just updating, I see that tile being duplicated so I see the new change & the old change.
Here's my setup:
final ref = FirebaseDatabase.instance.reference();
late DatabaseReference itemRef;
late FirebaseDatabase database = FirebaseDatabase();
late StreamSubscription _objectInfoStreamSub; // Not sure if needed?
late List<CustomObject> data = [];
#override
void initState() {
super.initState();
final keys = Global.kData.keys;
for (final key in keys) {
// Initialize this...
itemRef = database.reference().child('ABC').child(key.toString());
}
// Load the data...
_setListeners();
}
// Here is where I load my data initially...
Future<void> _setListeners() async {
// Clear out our data before we reload...
data.clear();
final keys = Global.kData.keys;
for (final key in keys) {
_objectInfoStreamSub =
ref.child("ABC").child(key.toString()).onValue.listen(
(event) {
setState(() {
// Mapped the data...
final firebaseData = Map<String, dynamic>.from(event.snapshot.value);
// Create the Room...
final room = CustomObject.fromJson(firebaseData);
// Check if blocked...
// Logic to see if user is blocked
// check if isArchived
// Logic to see if room is archvied
if (!isBlocked && !isArchived) {
if (!data.contains(room)) {
data.add(room);
}
}
// Sort by date
// Logic to sort so the most recent is at top
});
},
);
}
}
// Here is my updated StreamBuilder...
SliverToBoxAdapter(
child: StreamBuilder<dynamic>(
stream: itemRef.child('ABC').onValue,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(
child: CircularProgressIndicator(),
);
}
if (snapshot.hasData &&
snapshot.connectionState == ConnectionState.active) {
return ListView.builder(
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: data.length,
itemBuilder: (BuildContext context, int index) {
return ChatRoom(
data: data[index],
);
},
);
} else {
return Container();
}
},
),
),
Not sure if this causes your problem, but try wrapping the ListView.builder() with StreamBuilder instead of only it's items. Because in your current state of code, if you would add another item and your data.length would change, the ListView.builder() wouldn't get rebuilt and it wouldn't build new data.

Do Firebase and Flutter support one-time reads on the web?

I'm using Firebase and Flutter to read a List of Objects (EspecieModel). It's working perfect in IOS and Android, however It doesn't work on the Web (an empty List is retrieved).
I'm reading from Firebase as follows ...
Future<List<EspecieModel>> cargarTipoEspecie() async {
final List<EspecieModel> tipoEspecie = [];
Query resp = db.child('PATH/tipoespecie');
resp.onChildAdded.forEach((element) {
final temp = EspecieModel.fromJson(Map<String,dynamic>.from(element.snapshot.value));
temp.idEspecie = element.snapshot.key;
tipoEspecie.add(temp);
});
await resp.once().then((snapshot) {
print("Loaded - ${tipoEspecie.length}");
});
return tipoEspecie;
}
And I'm using a Future Builder to display the information...
FutureBuilder(
future: _tipoEspecieBloc.cargarTipoEspecie(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
// print(snapshot.connectionState);
if (snapshot.connectionState == ConnectionState.done && snapshot.hasData{
// print(snapshot.data);
final _especies = snapshot.data;
return Stack(
children: <Widget>[
ListView.builder(
itemCount: _especies!.length,
itemBuilder: (context, i) {
return _crearItem(context, _especies[i], i);
},
),
],
);
} else if (snapshot.hasError) {
print(snapshot.error);
return Text(snapshot.error.toString());
}
else {
return //CircleProgressIndicator Code
);
}
},
),
I can't identify what I'm doing wrong
How to do a one-time Firebase Query that works well on IOS, Android, and also on the Web??
This won't work:
resp.onChildAdded.forEach((element) {
final temp = EspecieModel.fromJson(Map<String,dynamic>.from(element.snapshot.value));
temp.idEspecie = element.snapshot.key;
tipoEspecie.add(temp);
});
await resp.once().then((snapshot) {
print("Loaded - ${tipoEspecie.length}");
});
return tipoEspecie;
The onChildAdded is not part of the await, so I doubt everything waits the way you seem to want. Just adding await in one place, does not make the rest of your code synchronous.
Instead consider using just once() and then populating your tipoEspecie array by looping over snapshot.value.values (a relatively new addition to the API).
var snapshot = await resp.once();
snapshot.value.values.forEach((node) {
final temp = EspecieModel.fromJson(Map<String,dynamic>.from(node.value));
temp.idEspecie = node.key;
tipoEspecie.add(temp);
});
return tipoEspecie;
Note: I'm not completely sure of the .forEach and the code in there. So if you get errors there, check what type you get back from .values and what node is, to get the correct key and values from it.

Flutter FutureBuilder wait for database connection in SQFLite

I'm using SQFLite to store data in the flutter application. The database provider is
class MessageDatabaseProvider {
Database db;
Future open() async {
final path = join(await getDatabasesPath(), databaseName);
db = await openDatabase(
path,
version: 1,
onCreate: (Database db, int version) async {
await db.execute('''
create table $tableMessage (
$columnId integer primary key autoincrement,
$columnCountryCode text not null,
$columnPhoneNumber text not null,
$columnMessage text null,
$columnCreated integer null)
''');
}
);
}
Future<List<Message>> list() async {
List<Map> maps = await db.query(tableMessage,
columns: [columnId, columnCountryCode, columnPhoneNumber, columnMessage, columnCreated],
orderBy: columnId
);
List<Message> list = [];
maps.forEach((element) {
list.add(Message.fromMap(element));
});
return list;
}
}
and Building a ListView
class _MessageHistoryListState extends State<MessageHistoryList> {
final _database = MessageDatabaseProvider();
#override
Widget build(BuildContext context) {
return Center(
child: _buildMessageHistory(),
);
}
Widget _buildMessageHistory() {
_database.open();
return FutureBuilder<List>(
future: _getList(),
initialData: [],
builder: (context, snapshot) {
print(snapshot);
return snapshot.hasData ?
ListView.builder(
padding: EdgeInsets.all(16.0),
itemCount: snapshot.data.length,
itemBuilder: (context, i) {
return _buildRow(snapshot.data[i]);
},
)
: Center(
child: CircularProgressIndicator(),
);
},
);
}
}
It shows the list of items the first time, then after changing navigation it gives error
AsyncSnapshot<List<dynamic>>(ConnectionState.done, null, NoSuchMethodError: The method 'query' was called on null.
You cannot use openDatabase until you close it by using the following line
await db.close();
you should call _database.open(); from init() if you are not recreating the widget. Remove _database.open(); from _buildMessageHistory()

Flutter - How do I use await inside the streambuilder?

I want to use await inside streambuilder. However, if you use async inside, you get an error. On the code below !!!!!!!! That's the part I want to solve. Thank you very much if I can tell you how.
class _MemoStreamState extends State<MemoStream> {
final _fireStore = Firestore.instance;
#override
Widget build(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: _fireStore
.collection(widget.logInUsrEmail)
.orderBy('id', descending: false)
.snapshots(),
builder: (context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (!snapshot.hasData) return LinearProgressIndicator();
final memos = snapshot.data.documents;
List<MemoMaterial> memoList = [];
for (var memo in memos) {
final memoDocumentID = memo.documentID;
final memoTitle = await PlatformStringCryptor().decrypt(memo.data['title'], _key); !!!!!!!!!!
final memoUsrID = memo.data['usrID'];
final memoUsrPW = memo.data['usrPW'];
final memoText = memo.data['text'];
final memoCreateTime = memo.data['createTime'];
final memoMaterial = MemoMaterial(
logInUsrEmail: widget.logInUsrEmail,
doc: memoDocumentID,
title: memoTitle,
usrID: memoUsrID,
usrPW: memoUsrPW,
text: memoText,
createTime: memoCreateTime,
);
memoList.add(memoMaterial);
}
return Expanded(
child: new ListView.builder(
You should do something like this :
Stream<List<MemoMaterial>> memosStream;
Future<MemoMaterial> generateMemoMaterial(Memo memo) async {
final memoTitle =
await PlatformStringCryptor().decrypt(memo.data['title'], _key);
return MemoMaterial(
logInUsrEmail: widget.logInUsrEmail,
doc: memo.documentID,
title: memoTitle,
usrID: memo.data['usrID'],
usrPW: memo.data['usrPW'],
text: memo.data['text'];,
createTime: memo.data['createTime'],
);
}
#override
void initState() {
memosStream = _fireStore
.collection(widget.logInUsrEmail)
.orderBy('id', descending: false)
.snapshots()
.asyncMap((memos) => Future.wait([for (var memo in memos) generateMemoMaterial(memo)]));
super.initState();
}
#override
Widget build(BuildContext context) {
return StreamBuilder<List<MemoMaterial>>(
stream: memosStream // Use memostream here
asyncMap() will "transform" every new set of Documents into a list of MemoMaterial, and emit this list into the stream when the action is performed.
Future.wait() allows to perform multiple async requests simultaneously.
You can do it using FutureBuilder inside StreamBuilder in following way.
Stream<List<int>> callme() async* {
yield [1, 2, 3, 4, 5, 6];
}
buildwidget() async {
await Future.delayed(Duration(seconds: 1));
return 1;
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: StreamBuilder(
stream: callme(),
builder: (_, sna) {
if (sna.hasData) {
return FutureBuilder(
future: buildwidget(),
builder: (_, snap) {
if (snap.hasData) {
return ListView.builder(
itemCount: sna.data.length,
itemBuilder: (_, index) {
return Text("${sna.data[index]} and ${snap.data}");
},
);
} else {
return CircularProgressIndicator();
}
},
);
} else {
return CircularProgressIndicator();
}
}),
),
);
}
I will prefer to use Getx or Provider State management to Handle the UI if it depends on the async function.
Suppose you want to fetch data from firebase using StreamBuilder() which returns some docs which contains image links then you want to download these images and show from storage. Obviously downloading the image is async type of work. Then you will get error if you show the images with the links you get direct from StreamBuilder().
What you can do is set a variable in getx or provider to show or hide the image Widget. If the Image is being downloaded or not downloaded then set the variable to hide/show the image when the async type of function is completed.