JSON file not loading on Flutter - flutter

I am new to Flutter, am currently stuck with my JSON file content not populating on my emulator. It shows no error, yet not displaying. If I include the content directly in my codeblock, it works fine. I can't spot the issue.
Below are my codes:
main.dart code
import 'package:emailapp/messagelist.dart';
import 'package:flutter/material.dart';
void main() => runApp(EmailApp());
class EmailApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.green
),
home: MessageList(title: 'Muss Mailer APP'),
);
}
}
messagelist.dart
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class MessageList extends StatefulWidget{
final String title;
const MessageList({Key key, this.title}) : super(key: key);
#override
State<StatefulWidget> createState()=>_MessageListState();
}
class _MessageListState extends State<MessageList>{
var messages=const [];
Future loadMessageList() async{
var content=await rootBundle.loadString('data/message.json');
print(content);
var collection=json.decode(content);
setState(() {
messages=collection;
});
}
void initstate() {
loadMessageList();
super.initState();
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: ListView.separated(
separatorBuilder: (context,index)=>Divider(),
itemCount: messages.length,
itemBuilder: (BuildContext context, int index){
var message=messages[index];
return ListTile(
isThreeLine: true,
leading: CircleAvatar( child: Text('AJ'),),
title: Text(message['subject']),
subtitle: Text(message['body'],maxLines: 2, overflow: TextOverflow.ellipsis,),
) ;
},
),
);
}
}
message.json
[ {
"subject":"My First Message",
"body":"Hello Form the other side of life fellas.. and happy to meet you guys"
},
{
"subject":"My Second Message",
"body":"Hello Form the other side of life fellas.. and happy to meet you guys"
},
{
"subject":"My Third Message",
"body":"Hello Form the other side of life fellas.. and happy to meet you guys"
},
{
"subject":"My Fourth Message",
"body":"Hello Form the other side of life fellas.. and happy to meet you guys"
},
{
"subject":"My Fifth Message",
"body":"Hello Form the other side of life fellas.. and happy to meet you guys"
},
{
"subject":"My Sixth Message",
"body":"Hello Form the other side of life fellas.. and happy to meet you guys"
}]
pubspec.yaml
assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
- data/message.json

It's just a spelling mistake. It's not initstate. Its initState (S is capital).
void initState() {
loadMessageList();
super.initState();
}

I would suggest making the following changes, to resolve some basic issues:
add Override notation to initState and use S in initState name:
as suggested by #Crazy Lazy Cat
#override
void initState() {
loadMessageList();
super.initState();
}
remove the use of setState inside the initState, it is not recommended to set state of a widget which has not been built yet:
Future loadMessageList() async{
// everything else, as it is
messages = collection;
}
remove const [] from the messages, since it is replaced in the initState anyways.
since the loadMessageList is async method you should use FutureBuilder for this purpose:
FutureBuilder(
future: loadMessageList,
builder: (context, snapshot) => snapshot.hasData ? your_widget() : Container(),
),
your_widget() widget refers to the ListView.separated widget in the build method.
create var messages = snapshot.data; before defining the ListView.separated code.
for this to work you need to change the code in loadMessageList:
Future loadMessageList() async {
// everything else, as it is
//messages = collection;
return collection;
}
No need of global messages variable now.

Related

How can I read text from files and display them as list using widget/s in Flutter?

may I ask a way how to make this work.
I have a text file named questions.txt.
This file contains the following questions:
How old are you?
Where do you live?
What is your age?
I want to load these questions from the file, and render them as a list in Flutter.
questionnaires.dart
import 'package:flutter/material.dart';
class Questionnaires extends StatefulWidget {
#override
_QuestionnairesState createState() => _QuestionnairesState();
}
class _QuestionnairesState extends State<Questionnaires> {
String q1 = "";
String q2 = "";
String q3 = "";
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
resizeToAvoidBottomInset: false,
body: Center(
child: Column(
children: <Widget>[
Text(q1),
Text(q2),
Text(q3)
],
),
),
),
);
}
}
You can start with the most basic way of retrieving the questions from a .txt file using rootBundle.loadString, then display it using a ListView widget.
main.dart
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Questions',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyAppScreen(),
);
}
}
class MyAppScreen extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return MyAppScreenState();
}
}
class MyAppScreenState extends State<MyAppScreen> {
List<String> _questions = [];
Future<List<String>> _loadQuestions() async {
List<String> questions = [];
await rootBundle.loadString('path/to/questions.txt').then((q) {
for (String i in LineSplitter().convert(q)) {
questions.add(i);
}
});
return questions;
}
#override
void initState() {
_setup();
super.initState();
}
_setup() async {
// Retrieve the questions (Processed in the background)
List<String> questions = await _loadQuestions();
// Notify the UI and display the questions
setState(() {
_questions = questions;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Flutter Questions")),
body: Center(
child: Container(
child: ListView.builder(
itemCount: _questions.length,
itemBuilder: (context, index) {
return Text(_questions[index]);
},
),
),
),
);
}
}
And here are the sample list of questions.
questions.txt
"How old are you?"
"Where do you live?"
"What is your age?"
In the example code above, you are parsing the text file line by line, please see LineSplitter. This is good for small and sample projects while you're testing out Flutter. But you should be able to update this implementation by following the official docs of Flutter, on how you can read from and write on files.
Furthermore, if you want to go big with your Flutter project, you should look into ways on how you can host your questions online, eg. served via REST APIs, then retrieve it using the http plugin for Flutter.
More on:
https://flutter.dev/docs/cookbook/networking/fetch-data
https://flutter.dev/docs/cookbook/persistence/reading-writing-files
https://pub.dev/packages/path_provider
Output:

Flutter FutureBuilder refresh when TextField value changes

The _futureData is to used for the FutureBuilder after retrieving value from the _loadPhobias() function.
entry_screen.dart
Future _futureData;
final TextEditingController _textEditingController = TextEditingController();
_loadPhobias() function does not seem to have any problem.
entry_screen.dart
Future<List<String>> _loadPhobias() async =>
await rootBundle.loadString('assets/phobias.txt').then((phobias) {
List _listOfAllPhobias = [];
List<String> _listOfSortedPhobias = [];
_textEditingController.addListener(() {
...
});
return _listOfSortedPhobias;
});
#override
void initState() {
super.initState();
_futureData = _loadPhobias();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: TextField(
// When the value is changed, the value returned from the _loadPhobias will also change. So I want the FutureBuilder to be rebuilt.
onChanged: (text) { setState(() => _futureData = _loadPhobias()) },
),
),
body: FutureBuilder(
future: _futureData,
builder: (context, snapshot) {
return snapshot.hasData
? ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index) => Column(
children: <Widget>[
PhobiasCard(sentence: snapshot.data[index]),
)
],
))
: Center(
child: CircularProgressIndicator(),
);
},
),
),
);
}
This is the error that I got:
FlutterError (setState() callback argument returned a Future.
The setState() method on _EntryScreenState#51168 was called with a closure or method that returned a Future. Maybe it is marked as "async".
Instead of performing asynchronous work inside a call to setState(), first execute the work (without updating the widget state), and then synchronously update the state inside a call to setState().)
The first thing to note, you mentioned that you want to rebuild your app every time there's a change in the text. For that to happen, you should use StreamBuilder instead. FutureBuilder is meant to be consumed once, it's like a fire and forget event or Promise in JavaScript.
Here's to a good comparison betweenStreamBuilder vs FutureBuilder.
This is how you would refactor your code to use StreamBuilder.
main.dart
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyAppScreen(),
);
}
}
class MyAppScreen extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return MyAppScreenState();
}
}
class MyAppScreenState extends State<MyAppScreen> {
StreamController<List<String>> _phobiasStream;
final TextEditingController _textEditingController = TextEditingController();
void _loadPhobias() async =>
await rootBundle.loadString('lib/phobia.txt').then((phobias) {
List<String> _listOfSortedPhobias = [];
for (String i in LineSplitter().convert(phobias)) {
for (String t in _textEditingController.text.split('')) {
if (i.split('-').first.toString().contains(t)) {
_listOfSortedPhobias.add(i);
}
}
}
_phobiasStream.add(_listOfSortedPhobias);
});
#override
void initState() {
super.initState();
_phobiasStream = StreamController<List<String>>();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: TextField(
controller: _textEditingController,
onChanged: (text) {
print("Text $text");
_loadPhobias();
},
),
),
body: StreamBuilder(
stream: _phobiasStream.stream,
builder: (context, snapshot) {
return snapshot.hasData
? Container(
height: 300,
child: ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
print("Data ${snapshot.data[index]}");
return Text(snapshot.data[index]);
},
),
)
: Center(
child: CircularProgressIndicator(),
);
},
),
);
}
}
As seen in the code above, I eliminated unnecessary text change callbacks inside the for a loop.
lib/phobia.txt
test1-test2-test3-test4-test5
Let me know if this is the expected scenario.
Hope this helps.
The solution can be inferred in the third line of the error message:
Instead of performing asynchronous work inside a call to setState(), first execute the work (without updating the widget state), and then synchronously update the state inside a call to setState().)
So this means you'll have to perform the operation before refreshing the widget. You can have a temporary variable to hold the result of the asynchronous work and use that in your setState method:
onChanged: (text) {
setState(() => _futureData = _loadPhobias())
},
Could be written as:
onChanged: (text) async {
var phobias = _loadPhobias();
setState(() {
_futureData = phobias;
});
},

Unnecessary Widget Rebuilds While Using Selector (Provider) inside StreamBuilder

I am using a Selector which rebuilds when a data in Bloc changes. Which woks fine but when the data changes it reloads the whole tree not just the builder inside Selector.
In my case the selector is inside a StreamBuilder. I need this because the stream is connected to API. So inside the stream I am building some widget and One of them is Selector. Selector rebuilds widgets which is depended on the data from the Stream.
Here is My Code. I dont want the Stream to be called again and again. Also the Stream gets called because the build gets called every time selector widget rebuilds.
main.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:provider_test/data_bloc.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MultiProvider(providers: [
ChangeNotifierProvider<DataBloc>(
create: (_) => DataBloc(),
)
], child: ProviderTest()),
);
}
}
class ProviderTest extends StatefulWidget {
#override
_ProviderTestState createState() => _ProviderTestState();
}
class _ProviderTestState extends State<ProviderTest> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: <Widget>[
Text("Outside Stream Builder"),
StreamBuilder(
stream: Provider.of<DataBloc>(context).getString(),
builder: (_, AsyncSnapshot<String> snapshot) {
if (snapshot.hasData) {
return Column(
children: <Widget>[
Text("Widget Generated by Stream Data"),
Text("Data From Strem : " + snapshot.data),
RaisedButton(
child: Text("Reload Select"),
onPressed: () {
Provider.of<DataBloc>(context, listen: false).changeValue(5);
}),
Selector<DataBloc, int>(
selector: (_, val) =>
Provider.of<DataBloc>(context, listen: false).val,
builder: (_, val, __) {
return Container(
child: Text(val.toString()),
);
}),
],
);
}
return Container();
},
)
],
),
);
}
}
bloc.dart
import 'package:flutter/foundation.dart';
class DataBloc with ChangeNotifier {
int _willChange = 0;
int get val => _willChange;
void changeValue(int val){
_willChange++;
notifyListeners();
}
Stream<String> getString() {
print("Stream Called");
return Stream.fromIterable(["one", "two", "three"]);
}
}
Also if I remove the StreamBuilder then the Selector acts like its suppose to. Why does StreamBuilder Rebuilds in this case? Is there anyway to prevent this?
Based on the code that you've shared, you can create a listener to your Stream on your initState that updates a variable that keeps the most recent version of your data, and then use that variable to populate your widgets. This way the Stream will only be subscribed to the first time the Widget loads, and not on rebuilds. I can't test it directly as I don't have your project. But please try it out.
Code example based on your code
class ProviderTest extends StatefulWidget {
#override
_ProviderTestState createState() => _ProviderTestState();
}
class _ProviderTestState extends State<ProviderTest> {
String _snapshotData;
#override
void initState() {
listenToGetString();
super.initState();
}
void listenToGetString(){
Provider.of<DataBloc>(context).getString().listen((snapshot){
setState(() {
_snapshotData = snapshot.data;
});
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: <Widget>[
Text("Outside Stream Builder"),
Column(
children: <Widget>[
Text("Widget Generated by Stream Data"),
Text("Data From Strem : " + _snapshotData),
RaisedButton(
child: Text("Reload Select"),
onPressed: () {
Provider.of<DataBloc>(context, listen: false).changeValue(5);
}
),
Selector<DataBloc, int>(
selector: (_, val) =>
Provider.of<DataBloc>(context, listen: false).val,
builder: (_, val, __) {
return Container(
child: Text(val.toString()),
);
}
),
],
)
],
),
);
}
}
I found the problem after reading this blog post here. I lacked the knowlwdge on how the Provider lib works and how its doing all the magic stuff out of Inherited widgets
The point and quote that solves this problem is. ( A quation from the blog post above)
When a Widget registers itself as a dependency of the Provider’s
InheritedWidget, that widget will be rebuilt each time a variation in
the “provided data” occurs (more precisely when the notifyListeners()
is called or when a StreamProvider’s stream emits new data or when a
FutureProvider’s future completes).
That means the variable that i am changing and the Stream that i am listning to, exists in the Same Bloc! that was the mistake. So when I change the val and call notifyListener() in a single bloc, all things reloads which is the default behaviour.
All I had to do to solve this problem is to make another Bloc and Abstract the Stream to that particular bloc(I think its a Good Practice also). Now the notifyListener() has no effect on the Stream.
data_bloc.dart
class DataBloc with ChangeNotifier {
int _willChange = 0;
String data = "";
int get val => _willChange;
void changeValue(int val){
_willChange++;
notifyListeners();
}
Future<String> getData () async {
return "Data";
}
}
stream_bloc.dart
import 'package:flutter/foundation.dart';
class StreamBloc with ChangeNotifier {
Stream<String> getString() {
print("Stream Called");
return Stream.fromIterable(["one", "two", "three"]);
}
}
And the problem is solved. Now the Stream will only be called if its invoked but not when the variable changes in the data_bloc

How to use dart.core.sink in flutter

import 'package:flutter/material.dart';
import 'dart:async';
void main() => runApp(MyApp());
//Using Bloc
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: bloc.darkThemeEnabled,
initialData: false,
builder: (context, snapshot) => MaterialApp(
theme: snapshot.data ? ThemeData.dark() : ThemeData.light(),
home: HomePage(snapshot.data)),
);
}
}
class HomePage extends StatelessWidget {
final bool darkThemeEnabled;
HomePage(this.darkThemeEnabled);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Dynamic Theme"),
),
body: Center(
child: Text("Hello World"),
),
drawer: Drawer(
child: ListView(
children: <Widget>[
ListTile(
title: Text("Dark Theme"),
trailing: Switch(
value: darkThemeEnabled,
onChanged: bloc.changeTheme,
),
)
],
),
),
);
}
}
class Bloc {
final _themeController = StreamController<bool>();
get changeTheme => _themeController.sink.add;
get darkThemeEnabled => _themeController.stream;
}
final bloc = Bloc();
1.A warning says to Close instances of dart.core.sink
2.Why dart.core.sink is used in flutter?
3.How can I solve this error
4.Its error documentation redirects me to this website link
5.I don't know how to use these methods in flutter please guide me
dart.core.sink is an interface that is implemented by Stream.
The warning is showing, because the dart compiler wants you to .close() your instance of a Stream. In this case that is your final _themeController = StreamController<bool>().
If you want to fix the warning, add
void dispose() {
_themeController.close();
}
to your Bloc class.
Just adding the method is not doing much, since it's not called. So you should change your main() method to call bloc.dispose() after runApp(MyApp()).
That error occur when missing close StreamController.
Simple way to fix:
Create abstract class:
abstract class Bloc {
void dispose();
}
Your bloc class implements Bloc, now you can close StreamController in dispose:
class ColorBloc implements Bloc {
// streams of Color
StreamController streamListController = StreamController<Color>.broadcast();
// sink
Sink get colorSink => streamListController.sink;
// stream
Stream<Color> get colorStream => streamListController.stream;
// function to change the color
changeColor() {
colorSink.add(getRandomColor());
}
// Random Colour generator
Color getRandomColor() {
Random _random = Random();
return Color.fromARGB(
_random.nextInt(256),
_random.nextInt(256),
_random.nextInt(256),
_random.nextInt(256),
);
}
// close Stream
#override
void dispose() {
streamListController.close();
}
}

StreamBuilder not re-rendering the widget inside?

I created this code, what i want to happen is when i press on the button i want the piechart to re-render with the new values (which should be old values but the food value increased by 1)
I am using a piechart from pie_chart: 0.8.0 package.
Deposit is nothing but a pojo (String category and int deposit)
the bloc.dart contains a global instance of the bloc, a getter for the stream and initialization of a stream of type
Here's my code:
import 'package:flutter/material.dart';
import 'package:pie_chart/pie_chart.dart';
import 'bloc.dart';
import 'Deposit.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'bloc Chart',
theme: ThemeData(
primarySwatch: Colors.blueGrey,
),
home: HomePage(),
);
}
}
class HomePage extends StatelessWidget {
Map<String, double> datamap = new Map();
#override
Widget build(BuildContext context) {
datamap.putIfAbsent("Food", () => 5);
datamap.putIfAbsent("transportation", () => 3);
return Scaffold(
appBar: AppBar(
title: Text("PieChart using blocs"),
),
body: Column(
children: <Widget>[
StreamBuilder<Deposit>(
stream: bloc.data, //A stream of Deposit data
builder: (context, snapshot) {
addDeposit(Deposit("Food", 1), datamap);
debugPrint("Value of food in map is: ${datamap["Food"]}");
return PieChart(dataMap: datamap);
}),
SizedBox.fromSize(
size: Size(20, 10),
),
RaisedButton(
onPressed: () {
bloc.add(Deposit("Food", 1)); //returns the stream.add
},
child: Icon(Icons.add),
),
],
),
);
}
void addDeposit(Deposit dep, Map<String, double> map) {
if (map.containsKey(dep.category)) {
map.update(dep.category, (value) => value + dep.price);
} else
map.putIfAbsent(dep.category, () => dep.price);
}
}
I think your problem is that the stream doesn't trigger new events. You don't have to close the stream to rebuild. I can't see anywhere in your code where you are triggering new events for the stream. Check below code to see a simple way how you can update a StatelessWidget using a StreamBuilder.
class CustomWidgetWithStream extends StatelessWidget {
final CustomBlock block = CustomBlock();
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
StreamBuilder(
stream: block.stream,
builder: (context, stream) {
return Text("${stream.data.toString()}");
}),
RaisedButton(
onPressed: () {
block.incrementNumber();
},
child: Text("Increment"),
)
],
);
}
}
class CustomBlock {
num counter = 10;
final StreamController<num> _controller = StreamController();
Stream<num> get stream => _controller.stream;
CustomBlock() {
_controller.onListen = () {
_controller.add(counter); // triggered when the first subscriber is added
};
}
void incrementNumber() {
counter += 1;
_controller.add(counter); // ADD NEW EVENT TO THE STREAM
}
dispose() {
_controller.close();
}
}
Although this is a working code snippet, I would strongly suggest to change your widget from StatelessWidget to StatefulWidget, for two reasons:
* if you go "by the book", if a widget changes the content by itself, then it's not a StatelessWidget, a stateless widget only displays data that is given to it. In your case, the widget is handling the tap and then decides what to do next and how to update itself.
* if you are using streams, in a stateful widget you can safely close the stream, as you can see in the above code, there's no safe way to close the stream. If you don't close the stream, there might be unwanted behaviour or even crashes.
This is my bloc file
import 'package:rxdart/rxdart.dart';
import 'package:testing/Deposit.dart';
class Bloc{
final _data = new BehaviorSubject<Deposit>();
Stream<Deposit> get data => _data.stream;
Function(Deposit) get add => _data.sink.add;
void dispose(){
_data.close();
}
}
Bloc bloc = new Bloc();