Dart append to file when i use transform in read text from file - dart

in this simple code i can show all fetched ids when finished reading file and get id from text file, but i want to append this fetched id inside JsonObjectTransformer class, not finished reading file
Future<void> main() async {
final ids = await File('sample.json')
.openRead()
.transform(const Utf8Decoder())
.transform<dynamic>(JsonObjectTransformer())
.map((dynamic json) => json['id'] as String)
.toList();
print(ids); // [#123456, #123456]
}
class JsonObjectTransformer extends StreamTransformerBase<String, dynamic> {
static final _openingBracketChar = '{'.codeUnitAt(0);
static final _closingBracketChar = '}'.codeUnitAt(0);
#override
Stream<dynamic> bind(Stream<String> stream) async* {
final sb = StringBuffer();
var bracketsCount = 0;
await for (final string in stream) {
for (var i = 0; i < string.length; i++) {
final current = string.codeUnitAt(i);
sb.writeCharCode(current);
if (current == _openingBracketChar) {
bracketsCount++;
}
if (current == _closingBracketChar && --bracketsCount == 0) {
yield json.decode(sb.toString());
sb.clear();
}
}
}
/*for example this line*/
//new File('test.txt').writeAsStringSync(sb.toString(), mode: FileMode.APPEND);
}
}
how can i do that?

There are multiple ways to do this but a simple way is to change the JsonObjectTransformer like this:
class JsonObjectTransformer extends StreamTransformerBase<String, dynamic> {
static final _openingBracketChar = '{'.codeUnitAt(0);
static final _closingBracketChar = '}'.codeUnitAt(0);
#override
Stream<dynamic> bind(Stream<String> stream) async* {
final sb = StringBuffer();
var bracketsCount = 0;
final ioSink = File('test.txt').openWrite(mode: FileMode.append);
await for (final string in stream) {
for (var i = 0; i < string.length; i++) {
final current = string.codeUnitAt(i);
sb.writeCharCode(current);
if (current == _openingBracketChar) {
bracketsCount++;
}
if (current == _closingBracketChar && --bracketsCount == 0) {
final dynamic jsonObject = json.decode(sb.toString());
ioSink.writeln(jsonObject['id'] as String);
yield jsonObject;
sb.clear();
}
}
}
await ioSink.flush();
await ioSink.close();
}
}
A more clean solution (since we want some separate of concern) would be to make use of the Stream in your main to write the ID's as each object are parsed. An example how to do that would be:
Future<void> main() async {
final file = File('test.txt').openWrite(mode: FileMode.append);
final ids = <String>[];
await File('sample.json')
.openRead()
.transform(const Utf8Decoder())
.transform<dynamic>(JsonObjectTransformer())
.map((dynamic json) => json['id'] as String)
.forEach((id) {
file.writeln(id);
ids.add(id);
});
await file.flush();
await file.close();
print(ids); // [#123456, #123456]
}

Related

How to break the loop for a stream in dart?

I known the listen can be abort by StreamSubscription. But for some reason, I can not call listen for the File.openRead(). How can I abort the read operation stream?
import 'dart:io';
import 'dart:async';
class Reader {
Stream<int> progess(File file) async* {
var sum = 0;
var fs = file.openRead();
await for (var d in fs) {
// consume d
sum += d.length;
yield sum;
}
}
void cancel() {
// How to abort the above loop without using StreamSubscription returned by listen().
}
}
void main() async {
var reader = Reader();
var file = File.new("a.txt");
reader.progess(file).listen((p) => print("$p"));
// How to cancel it without
Future.delayed(Duration(seconds: 1), () { reader.cancel()});
}
You cannot cancel the stream subscription without calling cancel on the stream subscription.
You might be able to interrupt the stream producer in some other way, using a "side channel" to ask it to stop producing values. That's not a stream cancel, more like a premature stream close.
Example:
class Reader {
bool _cancelled = false;
Stream<int> progess(File file) async* {
var sum = 0;
var fs = file.openRead();
await for (var d in fs) {
// consume d
sum += d.length;
if (_cancelled) return; // <---
yield sum;
}
}
void cancel() {
_cancelled = true;
}
}
Another option is to create a general stream wrapper which can interrupt the stream. Maybe something like
import"dart:async";
class CancelableStream<T> extends Stream<T> {
final Stream<T> _source;
final Set<_CancelableStreamSubscription<T>> _subscriptions = {};
CancelableStream(Stream<T> source) : _source = source;
#override
StreamSubscription<T> listen(
onData, {onError, onDone, cancelOnError}) {
var sub = _source.listen(onData,
onError: onError, onDone: onDone, cancelOnError: cancelOnError);
var canSub = _CancelableStreamSubscription<T>(sub, this, cancelOnError ?? false);
_subscriptions.add(canSub);
return canSub;
}
void cancelAll() {
while (_subscriptions.isNotEmpty) {
_subscriptions.first.cancel();
}
}
}
class _CancelableStreamSubscription<T> implements StreamSubscription<T> {
final bool _cancelOnError;
final StreamSubscription<T> _source;
final CancelableStream<T> _owner;
_CancelableStreamSubscription(
this._source, this._owner, this._cancelOnError);
#override
Future<void> cancel() {
_owner._subscriptions.remove(this);
return _source.cancel();
}
#override
void onData(f) => _source.onData(f);
#override
void onError(f) {
if (!_cancelOnError) {
_source.onError(f);
} else {
_source.onError((Object e, StackTrace s) {
_owner._subscriptions.remove(this);
if (f is void Function(Object, StackTrace)) {
f(e, s);
} else {
f?.call(e);
}
});
}
}
#override
bool get isPaused => _source.isPaused;
#override
void onDone(f) => _source.onDone(() {
_owner._subscriptions.remove(this);
f?.call();
});
#override
void pause([resumeFuture]) => _source.pause(resumeFuture);
#override
void resume() => _source.resume;
#override
Future<E> asFuture<E>([E? value]) => _source.asFuture(value);
}
You can then use it like:
void main() async {
Stream<int> foo() async* {
yield 1;
yield 2;
yield 3;
yield 4;
}
var s = CancelableStream<int>(foo());
await for (var x in s) {
print(x);
if (x == 2) s.cancelAll();
}
}

Flutter FutureBuilder Not Updating

I have a Flutter FutureBuilder that needs to be updated with new data given by the user. However, the UI elements in the FutureBuilder do not update and still contain the old values. I have checked through print statements that the new data is correctly loaded. The issue seems to be with FutureBuilder rebuilding the widget when the new data is loaded. Any help is appreciated.
Future<List<PollItem>> fetchPost(String loc) async {
return new Future(() async {
final response = await http
.post(restip + '/getPosts',
body: {"data": loc});
if (response.statusCode == 200) {
print(response.body);
// If the call to the server was successful, parse the JSON
// This function adds json to list
PollItem.fromJson(json.decode(response.body));
// list is a list of posts gathered based on the string criteria
return list;
} else {
throw Exception('Failed to load polls');
}
});
}
class PollState extends State<Poll> {
TextEditingController textc = new TextEditingController();
static String dropDowntext = "City";
String _name = "Search";
final _names = [''];
Widget build(BuildContext context) {
print("dropdown"+dropDowntext);
textc.text = _name;
print(dropDowntext);
return FutureBuilder<List<PollItem>>(
future: fetchPost(dropDowntext),
initialData: [PollItem()],
builder: (context, snapshot) {
if (snapshot.hasData) {
print(snapshot.data[0].question);
});
}
Here is my global file:
List<PollItem> list = new List();
factory PollItem.fromJson(Map<String, dynamic> json) {
int len = json['length'];
if(listNum!=len) {
listNum = len;
list.clear();
for (int i = 0; i < len; i++) {
list.add(PollItem(
answer1: json[i.toString()]['answer1'],
location: json[i.toString()]['location']
)
);
}
}
}
You don't need to create a Future object :
Future<List<PollItem>> fetchPost(String loc) async {
final response = await http.post(restip + '/getPosts',body: {"data": loc});
if (response.statusCode == 200) {
print(response.body);
final data = json.decode(response.body);
int len = data['length'];
final List<PollItem> newList = List();
for (int i = 0; i < len; i++) {
newList.add(PollItem(
answer1: data[i.toString()]['answer1'],
location: data[i.toString()]['location']
)
);
}
print("new list size: ${newList.length}");
return newList;
} else {
throw Exception('Failed to load polls');
}
return null;
}

StreamBuilder throws dirty state

I am trying to fetch some data from the internet, store it to my sqlite database and display it on the screen using a ListView.
When I fetch the data for the first time everything works fine and I am able to see the data on the screen, the data is also inserted in the sqlite database, but when I reopen the app I get an error saying
flutter: The following NoSuchMethodError was thrown building StreamBuilder<StudentModel>(dirty, state:
flutter: _StreamBuilderBaseState<StudentModel, AsyncSnapshot<StudentModel>>#3f888):
flutter: The getter 'studentData' was called on null.
flutter: Receiver: null
flutter: Tried calling: studentData
Here is my model class
class StudentModel {
int status;
String msg;
StudentModelData studentModelData;
StudentModel({this.status, this.msg, this.studentModelData});
StudentModel.fromJson(Map<String, dynamic> json) {
status = json['status'];
msg = json['msg'];
studentModelData = json['data'] != null ? new StudentModelData.fromJson(json['data']) : null;
}
StudentModel.fromDb(Map<String, dynamic> parsedJson) {
status = parsedJson['status'];
msg = parsedJson['msg'];
studentModelData = parsedJson['data'] != null ? new StudentModelData.fromJson(parsedJson['data']) : null;
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['status'] = this.status;
data['msg'] = this.msg;
if (this.studentModelData != null) {
data['data'] = this.studentModelData.toJson();
}
return data;
}
}
class StudentModelData {
int lastIndex;
List<StudentData> studentData;
StudentModelData({this.lastIndex, this.studentData});
StudentModelData.fromJson(Map<String, dynamic> json) {
lastIndex = json['lastIndex'];
if (json['studentData'] != null) {
studentData = new List<StudentData>();
json['studentData'].forEach((v) {
studentData.add(new StudentData.fromJson(v));
});
}
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['lastIndex'] = this.lastIndex;
if (this.studentData != null) {
data['studentData'] = this.studentData.map((v) => v.toJson()).toList();
}
return data;
}
}
class StudentData {
String studentId;
String studName;
String studProfilepic;
String studentEmail;
String studentMobile;
String courseName;
String classCode;
int minAvg;
int avg;
StudentData(
{this.studentId,
this.studName,
this.studProfilepic,
this.studentEmail,
this.studentMobile,
this.courseName,
this.classCode,
this.minAvg,
this.avg});
StudentData.fromJson(Map<String, dynamic> json) {
studentId = json['student_id'];
studName = json['stud_name'];
studProfilepic = json['stud_profilepic'];
studentEmail = json['student_email'];
studentMobile = json['student_mobile'];
courseName = json['course_name'];
classCode = json['class_code'];
minAvg = json['minAvg'];
avg = json['avg'];
}
StudentData.fromDb(Map<String, dynamic> parsedJson){
studentId = parsedJson['student_id'];
studName = parsedJson['stud_name'];
studProfilepic = parsedJson['stud_profilepic'];
studentEmail = parsedJson['student_email'];
studentMobile = parsedJson['student_mobile'];
courseName = parsedJson['course_name'];
classCode = parsedJson['class_code'];
minAvg = parsedJson['minAvg'];
avg = parsedJson['avg'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['student_id'] = this.studentId;
data['stud_name'] = this.studName;
data['stud_profilepic'] = this.studProfilepic;
data['student_email'] = this.studentEmail;
data['student_mobile'] = this.studentMobile;
data['course_name'] = this.courseName;
data['class_code'] = this.classCode;
data['minAvg'] = this.minAvg;
data['avg'] = this.avg;
return data;
}
}
Following is my repository class
class StudentDbProvider implements Source, Cache {
Database db;
void init() async {
print("database initialized");
Directory documentsDirectory = await getApplicationDocumentsDirectory();
final path = join(documentsDirectory.path, "students.db");
db = await openDatabase(path, version: 1,
onCreate: (Database newDb, int version) {
newDb.execute("""
CREATE TABLE STUDENTS(
id INTEGER PRIMARY KEY,
student_id TEXT,
stud_name TEXT,
stud_profilepic TEXT,
student_email TEXT,
student_mobile TEXT,
course_name TEXT,
class_code TEXT,
minAvg TEXT,
avg TEXT
)
""");
});
}
#override
Future<int> clear() {
return db.delete("STUDENTS");
}
#override
Future<StudentModel> fetchStudents(String disciplineId, String schoolId,
String year_id, String lastIndex) async {
print("PritishSawant${db==null}");
final maps =
await db.query("STUDENTS");
if (maps.length > 0) {
return StudentModel.fromDb(maps.first);
}
return null;
}
#override
Future<int> addStudent(StudentData studentData) {
return db.insert("STUDENTS", studentData.toJson(),
conflictAlgorithm: ConflictAlgorithm.ignore);
}
}
final studentDbProvider = StudentDbProvider();
Following is my bloc class
class StudentsBloc {
final _repository = Repository();
final _students = PublishSubject<StudentModel>();
Observable<StudentModel> get students => _students.stream;
fetchStudents(String disciplineId,String schoolId,String year_id,String lastIndex) async {
await studentDbProvider.init();
final student = await _repository.fetchStudents(disciplineId, schoolId, year_id, lastIndex);
_students.sink.add(student);
}
clearCache(){
return _repository.clearCache();
}
dispose(){
_students.close();
}
}
As far as I can understand the error must be occurring due to improper database initialisation but when I did the first network request everything was working fine and I did not get any error in the console and the database was also initialised. I am not able to understand why the error is occurring for the second time onwards?
I suppose you are calling snapshot.data.studentData in some part of your code.
On a stream builder, I tend to first do a null check
if (snapshot.data != null) {
// your code here
}
And then proceed to verify the data, else you can use a getter on a null data provided by the StreamBuilder
You should check snapshot!=null && snapshot.hasError to ensure your data is actually returned
if (snapshot!=null && !snapshot.hasError) {
// your code here
}

Flutter Sqflite error says The method 'query' was called on null

I am trying to fetch some data from network and store it in sqlite database. Following is the model class
class StudentModel {
int status;
String msg;
StudentModelData studentModelData;
StudentModel({this.status, this.msg, this.studentModelData});
StudentModel.fromJson(Map<String, dynamic> json) {
status = json['status'];
msg = json['msg'];
studentModelData = json['data'] != null ? new StudentModelData.fromJson(json['data']) : null;
}
StudentModel.fromDb(Map<String, dynamic> parsedJson) {
status = parsedJson['status'];
msg = parsedJson['msg'];
studentModelData = studentModelData = jsonDecode(json['data']) != null ? new StudentModelData.fromJson(jsonDecode(json['data'])) : null;
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['status'] = this.status;
data['msg'] = this.msg;
if (this.studentModelData != null) {
data['data'] = this.studentModelData.toJson();
}
return data;
}
}
class StudentModelData {
int lastIndex;
List<StudentData> studentData;
StudentModelData({this.lastIndex, this.studentData});
StudentModelData.fromJson(Map<String, dynamic> json) {
lastIndex = json['lastIndex'];
if (json['studentData'] != null) {
studentData = new List<StudentData>();
json['studentData'].forEach((v) {
studentData.add(new StudentData.fromJson(v));
});
}
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['lastIndex'] = this.lastIndex;
if (this.studentData != null) {
data['studentData'] = this.studentData.map((v) => v.toJson()).toList();
}
return data;
}
}
class StudentData {
String studentId;
String studName;
String studProfilepic;
String studentEmail;
String studentMobile;
String courseName;
String classCode;
int minAvg;
int avg;
StudentData(
{this.studentId,
this.studName,
this.studProfilepic,
this.studentEmail,
this.studentMobile,
this.courseName,
this.classCode,
this.minAvg,
this.avg});
StudentData.fromJson(Map<String, dynamic> json) {
studentId = json['student_id'];
studName = json['stud_name'];
studProfilepic = json['stud_profilepic'];
studentEmail = json['student_email'];
studentMobile = json['student_mobile'];
courseName = json['course_name'];
classCode = json['class_code'];
minAvg = json['minAvg'];
avg = json['avg'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['student_id'] = this.studentId;
data['stud_name'] = this.studName;
data['stud_profilepic'] = this.studProfilepic;
data['student_email'] = this.studentEmail;
data['student_mobile'] = this.studentMobile;
data['course_name'] = this.courseName;
data['class_code'] = this.classCode;
data['minAvg'] = this.minAvg;
data['avg'] = this.avg;
return data;
}
}
And my database provider class looks like following
class StudentDbProvider implements Source, Cache {
Database db;
StudentDbProvider() {
init();
}
void init() async {
Directory documentsDirectory = await getApplicationDocumentsDirectory();
final path = join(documentsDirectory.path, "students.db");
db = await openDatabase(path, version: 1,
onCreate: (Database newDb, int version) {
newDb.execute("""
CREATE TABLE STUDENTS(
id INTEGER PRIMARY KEY,
status INTEGER,
msg TEXT,
data BLOB
)
""");
});
}
#override
Future<int> clear() {
return db.delete("STUDENTS");
}
#override
Future<StudentModel> fetchStudents(String disciplineId, String schoolId,
String year_id, String lastIndex) async {
final maps =
await db.query("STUDENTS");
if (maps.length > 0) {
return StudentModel.fromDb(maps.first);
}
return null;
}
#override
Future<int> addStudent(StudentModel studentModel) {
return db.insert("STUDENTS", studentModel.toJson(),conflictAlgorithm: ConflictAlgorithm.ignore);
}
}
final studentDbProvider = StudentDbProvider();
Whenever I tried to fetch the data and stored in the database, I get the following error in the console
NoSuchMethodError: The method 'query' was called on null.
Receiver: null
Tried calling: query("STUDENTS")
#0 Object.noSuchMethod (dart:core/runtime/libobject_patch.dart:50:5)
The data gets added to the database but I am not able to query the data from the database.
Reducing at minimum your example, this throws the exception The method 'query' was called on null
because fetch is executed before db is properly initialized:
class Database {
Future<int> query() {
return Future.value(1);
}
}
const oneSecond = Duration(seconds: 1);
class Provider {
Database db;
Provider() {
init();
}
void init() async {
db = await Future.delayed(oneSecond, () => Database());
}
Future<int> fetch() {
return db.query();
}
}
main() async {
var provider = Provider();
await provider.fetch();
}
The problem resides in calling an async method inside a constructor, see also:
Calling an async method from component constructor in Dart
This works:
class Database {
Future<int> query() {
return Future.value(1);
}
}
const oneSecond = Duration(seconds: 1);
class Provider {
Database db;
Provider() {
//init();
}
void init() async {
db = await Future.delayed(oneSecond, () => Database());
}
Future<int> fetch() {
return db.query();
}
}
main() async {
var provider = Provider();
await provider.init();
await provider.fetch();
}
Please note that init must be awaited, otherwise you will catch the same The method 'query' was called on null.
the problem is the init must be awaited. here what I did to fix it
_onCreate(Database db, int version) async {
await db.execute('CREATE TABLE ... <YOUR QUERY CREATION GOES HERE>');
}
Future<Database> getDatabaseInstance() async {
final String databasesPath = await getDatabasesPath();
final String path = join(databasesPath, '<YOUR DB NAME>');
return await openDatabase(path, version: 1, onCreate: _onCreate);
}
Future<int> save(Contact contact) {
return getDatabaseInstance().then((db) {
final Map<String, dynamic> contactMap = Map();
contactMap['name'] = contact.name;
contactMap['account_number'] = contact.accountNumber;
return db.insert('contacts', contactMap);
});
}
The SQFlite page gives a good example about it and helps a lot.
https://github.com/tekartik/sqflite/blob/master/sqflite/doc/opening_db.md

future functions keep repeating

am trying to read and write in files in my flutter app .. like this:
Future<String> get _localPath async {
print('hi');
final directory = await getApplicationDocumentsDirectory();
return directory.path;
}
Future<File> get _localFile async {
final path = await _localPath;
File f = File('$path/mypollshash.txt');
if (f.existsSync()) {
print('exists');
String contents = await f.readAsString();
content = contents;
fetchHash();
} else {
print('not exists');
fetch();
}
return f;
}
Future checkfileexist() async {
try {
final file = await _localFile;
String contents = await file.readAsString();
content = contents;
} catch (e) {
//return 'nothing';
}
}
Future<File> writehash(String hash) async {
final file = await _localFile;
return file.writeAsString('$hash', mode: FileMode.write);
}
Future<File> get _localjson async {
final path = await _localPath;
return File('$path/mypolls.json');
}
Future<File> writejson(String json) async {
final file = await _localjson;
return file.writeAsString('$json', mode: FileMode.write);
}
readjson() async {
try {
final file = await _localjson;
String contents = await file.readAsString();
content = contents;
setState(() {
polls = pollsFromJson(content);
isloading = false;
});
writejson(pollsToJson(polls));
writehash(polls.hash);
print('here');
// return contents;
} catch (e) {
fetch();
print('there');
print(e);
// If we encounter an error, return 0
//return 'nothing';
}
}
fetch() async {
String data =
await DefaultAssetBundle.of(context).loadString("assets/mypolls.json");
setState(() {
polls = pollsFromJson(data);
isloading = false;
});
writejson(pollsToJson(polls));
writehash(polls.hash);
}
fetchHash() async {
String data = await DefaultAssetBundle.of(context)
.loadString("assets/pollshash.json");
print(content);
final pollshash = pollshashFromJson(data);
if (content == pollshash.hash) {
print('take from the saved json');
readjson();
} else {
print('call api');
fetch();
}
}
and then am calling it here:
#override
void initState() {
super.initState();
checkfileexist();
}
this works fine .. but the method will keep called even when i go to another page and will get this printed over and over again:
I/flutter (17060): hi
I/flutter (17060): here
I/flutter (17060): exists
I/flutter (17060): d1f4bd60f52991d100adafa416f48b52
I/flutter (17060): take from the saved json
I want this to be called only once .. how to do this?
InitState is not called once it's normal. Instead you can do it with multiple solutions.
Create an attributes in your component to memorize if you already did your checks like this
class MyComponentState ... {
bool hasChecked = false;
bool isFileExists = false;
#override
initState() {
super.initState();
if(!hasChecked) {
this.hasChecked = true;
this.isFileExists = checkfileexist();
}
}
}

Resources