I'm new to flutter and was wondering if anyone would be able to guide me.
I would like to irritate through a string that I retrieved from a json file. I would like to display each letter separately. Example below.
Output
Complete Word: Hi
Letter pos 0: H
Letter pos 1: I
What I tried so far is adding a for loop below the itemBuilder but can't retrieve the variable inside the card. I tried adding a for loop inside the Widget and it doesn't allow me.
Any input would be greatly appreciated!
import 'dart:convert';
import 'package:flutter/material.dart';
void main() {
runApp(new MaterialApp(
home: new MyApp(),
));
}
class MyApp extends StatefulWidget {
#override
MyAppState createState() => new MyAppState();
}
class MyAppState extends State<MyApp> {
List data;
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("Load local JSON file"),
),
body: new Container(
child: new Center(
// Use future builder and DefaultAssetBundle to load the local JSON file
child: new FutureBuilder(
future: DefaultAssetBundle
.of(context)
.loadString('data_repo/starwars_data.json'),
builder: (context, snapshot) {
// Decode the JSON
var new_data = JSON.decode(snapshot.data.toString());
return new ListView.builder(
// Build the ListView
itemBuilder: (BuildContext context, int index) {
return new Card(
child: new Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
new Text("Complete Word: " + new_data[index]['complete_word'])
],
),
);
},
itemCount: new_data == null ? 0 : new_data.length,
);
}),
),
));
}
}
Create a method and within it create a list of widgets. Include the widgets and then return the list.
Example
child: new Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: ListMyWidgets()),
List<Widget> ListMyWidgets() {
List<Widget> list = new List();
list.add(new Text("hi"));
list.add(new Text("hi2"));
list.add(new Text("hi3"));
return list;
}
Related
I have a list of messages that I want to fill on init with a firebase collection.
import 'package:flutter/material.dart';
import 'package:my_first_flutter_app/chatmessage.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:logging/logging.dart';
final Logger log = new Logger('ChatScreen');
class ChatScreen extends StatefulWidget {
#override
State createState() => new ChatScreenState();
}
class ChatScreenState extends State<ChatScreen> {
final TextEditingController _chatController = new TextEditingController();
final List<ChatMessage> _messages = <ChatMessage>[];
#override
Widget build(BuildContext context) {
return new Column(
children: <Widget>[
new Flexible(
child: ListView.builder(
padding: new EdgeInsets.all(8.0),
reverse: true,
itemBuilder: (_, int index) => _messages[index],
itemCount: _messages.length,
),
),
new Divider(
height: 1.0,
),
new Container(decoration: new BoxDecoration(
color: Theme.of(context).cardColor,
),
child: _chatEnvironment(),)
],
);
}
}
I tried to do this:
#override
Widget build(BuildContext context) {
Firestore.instance
.collection('chats')
.document('ROOM_1')
.collection("messages")
.getDocuments()
.then((snap) {
return new Column(
....
but I need to return a Widget, while this attempt returns a Future.
How I can fill the _messages array with data coming from my firestore collection on the initialization of my chat screen page?
If you just need to display all messages in a ListView from the firestore collection, then maybe you'll love the StreamBuilder widget. You can do something like this:
return new Column(
children: <Widget>[
new Flexible(
child: StreamBuilder(
stream: Firestore.instance.collection('chats').document('ROOM_1').collection('messages').snapshots(),
builder: (context, snapshot){
if (!snapshot.hasData){
return Container(
child: Center(
child: Text("No data")
)
);
}
return ListView.builder(
padding: EdgeInsets.all(8.0),
reverse: true,
itemCount: snapshot.data.documents.length,
itemBuilder: (_, int index) {
return ChatMessage(text: snapshot.data.documents[index]["messageField"]); //I just assumed that your ChatMessage class takes a parameter message text
}
);
}
)
),
new Divider(
height: 1.0,
),
...
Note that in this example, I didn't use the _messages variable.
I currently have an ExpansionTile that has a ListView.builder which contains a ListTile.
The task I am doing is putting multiple listTiles under a single Expansiontile.
This is the file list.dart
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';
import 'dart:async' show Future;
import 'package:flutter/services.dart' show rootBundle;
import 'dart:convert';
import 'package:emas_app/model/accounts_model.dart';
Future<String> _loadAsset() async{
return await rootBundle.loadString('Assets/accounts.json');
}
Future<Accounts> loadAccounts() async{
final response = await _loadAsset();
final jsonResponse = json.decode(response);
Accounts accounts = new Accounts.fromJson(jsonResponse);
return accounts;
}
class ProviderList extends StatefulWidget {
#override
ListState createState() {
return new ListState();
}
}
class ListState extends State<ProviderList> {
#override
Widget build(BuildContext context) {
Widget body = new FutureBuilder<Accounts>(
future: loadAccounts(),
builder: (context, snapshot){
if(snapshot.hasData){
return new ListView.builder(
itemCount: snapshot.data.accountinfo.length,
itemBuilder: (context,index){
return new Card(
child: new Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
new ExpansionTile(
title: new Text("Johor"),
children: <Widget>[
new ListTile(
title: new Text("${snapshot.data.accountinfo[index].name}"),
)
]
),
],
),
);
});
}else{
return new Center(
child: new CircularProgressIndicator(),
);
}
});
return new Scaffold(
appBar: new AppBar(title: new Text("Providers")),
body: body
);
}
}
The main problem I am currently facing is the ExpansionTile keeps on repeating for each new ListTile.
Any thoughts on how should I properly place the ExpansionTile?
because you build
Card(
child: new Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
new ExpansionTile(
title: new Text("Johor"),
children: <Widget>[
new ListTile(
title: new Text("${snapshot.data.accountinfo[index].name}"),
)
]
),
],
Till = snapshot.data.accountinfo.length
try to create only ListTile to that length, not the whole card widget
I get the list of files from the user's folder. The names of the files I transfer to the ListView.builder. It's work, but I think, this is bad architecture.
A method _getFilesFromDir() call with a high frequency.
How to make the correct list generation, so as not to update the interface without changing the file list?
class CharacteristList extends StatefulWidget {
#override
_CharacteristListState createState() => new _CharacteristListState();
}
class _CharacteristListState extends State<CharacteristList> {
List<String> filesList = new List<String>();
List<String> filesL = new List<String>();
#override
void initState() {
super.initState();
filesList = [];
}
Future<List<String>> _getFilesFromDir() async{
filesL = await FilesInDirectory().getFilesFromDir();
setState(() {
filesList = filesL;
});
return filesList;
}
_getFilesCount(){
_getFilesFromDir();
int count = filesList.length;
return count;
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: const Text('Список документов'),
),
body: new Column(
children: <Widget>[
new Expanded(
child: new ListView.builder(
//TODO не успевает сформировать список файлов
itemCount: _getFilesCount(),
itemBuilder: (context, index){
return new CharacteristListItem(filesList[index]);
},
),
),
],
),
floatingActionButton: new FloatingActionButton(
onPressed: () {
Navigator.push(context,
new MaterialPageRoute(builder: (context)
=> new StartScreen()),
);},
child: new Icon(Icons.add),
),
);
}
}
// add dependancy in pubspec.yaml
path_provider:
import 'dart:io' as io;
import 'package:path_provider/path_provider.dart';
//Declare Globaly
String directory;
List file = new List();
#override
void initState() {
// TODO: implement initState
super.initState();
_listofFiles();
}
// Make New Function
void _listofFiles() async {
directory = (await getApplicationDocumentsDirectory()).path;
setState(() {
file = io.Directory("$directory/resume/").listSync(); //use your folder name insted of resume.
});
}
// Build Part
#override
Widget build(BuildContext context) {
return MaterialApp(
navigatorKey: navigatorKey,
title: 'List of Files',
home: Scaffold(
appBar: AppBar(
title: Text("Get List of Files with whole Path"),
),
body: Container(
child: Column(
children: <Widget>[
// your Content if there
Expanded(
child: ListView.builder(
itemCount: file.length,
itemBuilder: (BuildContext context, int index) {
return Text(file[index].toString());
}),
)
],
),
),
),
);
}
Don't call _getFilesCount() in build(). build() can be called very frequently. Call it in initState() and store the result instead of re-reading over and over again.
I changed the architecture of the class - I used FutureBuilder.
class _CharacteristListState extends State<CharacteristList> {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: const Text('Список документов'),
),
body: new Center(
child: new Column(
children: <Widget>[
new FutureBuilder(
future: _inFutureList(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if(snapshot.connectionState == ConnectionState.waiting){
return new Text('Data is loading...');
}
else{
return customBuild(context, snapshot);
}
}
)
],
),
),
floatingActionButton: new FloatingActionButton(
onPressed: () {
Navigator.push(context,
new MaterialPageRoute(builder: (context)
=> new StartScreen()),
);},
child: new Icon(Icons.add),
),
);
}
Widget customBuild(BuildContext context, AsyncSnapshot snapshot){
List<String> values = snapshot.data;
return new Container(
child: new Expanded(
child: new ListView.builder(
itemCount: values.length,
itemBuilder: (context, index){
return new CharacteristListItem(values[index]);
},
),
)
);
}
Future<List<String>>_inFutureList() async{
var filesList = new List<String>();
filesList = await FilesInDirectory().getFilesFromDir();
await new Future.delayed(new Duration(milliseconds: 500));
return filesList;
}
}
// add dependancy in pubspec.yaml
path_provider:
import 'dart:io' as io;
import 'package:path_provider/path_provider.dart';
//Declare Globaly
String directory;
List file = new List();
#override
void initState() {
// TODO: implement initState
super.initState();
_listofFiles();
}
// Make New Function
void _listofFiles() async {
directory = "/storage/emulated/0/Android/data/"; //Give your folder path
setState(() {
file = io.Directory("$directory/resume/").listSync(); //use your folder name insted of resume.
});
}
// Build Part
#override
Widget build(BuildContext context) {
return MaterialApp(
navigatorKey: navigatorKey,
title: 'List of Files',
home: Scaffold(
appBar: AppBar(
title: Text("Get List of Files with whole Path"),
),
body: Container(
child: Column(
children: <Widget>[
// your Content if there
Expanded(
child: ListView.builder(
itemCount: file.length,
itemBuilder: (BuildContext context, int index) {
return Text(file[index].toString());
}),
)
],
),
),
),
);
}
I have the following screen:
import 'package:flutter/material.dart';
import '../models/patient.dart';
import '../components/patient_card.dart';
import '../services.dart';
class Home extends StatefulWidget {
var patients = <Patient>[];
#override
_HomeState createState() => new _HomeState();
}
class _HomeState extends State<Home> {
#override
initState() {
super.initState();
Services.fetchPatients().then((p) => setState(() => widget.patients = p));
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Home'),
),
body: new Container(
child: new ListView(
children: widget.patients.map(
(patient) => new PatientCard(patient),
).toList()
)
)
);
}
}
As you can see I do the endpoint call when I overwrite initState() in _HomeState. But it only runs once initially when the app starts. I can't just type r in my terminal and let the app hot reload and call the endpoint again.. I have to use Shift + r to do a full restart first.
So the question is, am I calling the web service in the recommended spot? And if it not... where does it go? Also, shouldn't ListView have a function / property that gets called on "pull to refresh" or something?
As mentioned by #aziza you can use a Stream Builder or if you want to call a function every time widget gets built then you should call it in build function itself. Like in your case.
#override
Widget build(BuildContext context) {
Services.fetchPatients().then((p) => setState(() => widget.patients = p));
return new Scaffold(
appBar: new AppBar(
title: new Text('Home'),
),
body: new Container(
child: new ListView(
children: widget.patients.map(
(patient) => new PatientCard(patient),
).toList()
)
)
);
}
If you want to add pull-to-refresh functionality then wrap your widget in refresh indicator widget. Add your call in onRefresh property.
return new RefreshIndicator(child: //Your Widget Tree,
onRefresh: handleRefresh);
Note that this widget only works with vertical scroll view.
Hope it helps.
Have a look on StreamBuilder. This widget will allow you to deal with async data that are frequently updated and will update the UI accordingly by listening onValue at the end of your stream.
Flutter have FutureBuilder class, you can also create your widget as shown below
Widget build(BuildContext context) {
var futureBuilder = new FutureBuilder(
future: Services.fetchPatients().then((p) => setState(() => widget.patients = p)),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
if (snapshot.data != null) {
return new Container(
child: new ListView(
children: snapshot.data.map(
(patient) => new PatientCard(patient),
).toList()
)
);
}
} else {
return new Container(
alignment: Alignment.center,
padding: const EdgeInsets.all(16.0),
child: new CircularProgressIndicator());
}
});
return new Container(child: futureBuilder);
}
Example project : Flutter - Using the future builder with list view.
I have a local json file that I'm able to retrieve all the information I need. However, I can't seem to display it like I prefer. I have a list called list and would like to display each element (each letter) as a column so I can display it with a padding and change the font. I'm trying to create a game similar to word connect.
import 'dart:convert';
import 'package:flutter/material.dart';
void main() {
runApp(new MaterialApp(
home: new MyApp(),
));
}
class MyApp extends StatefulWidget {
#override
MyAppState createState() => new MyAppState();
}
class MyAppState extends State<MyApp> {
List data;
#override
Widget build(BuildContext context) {
final title = 'Basic List';
return new MaterialApp(
title: title,
home: new Scaffold(
appBar: new AppBar(
title: new Text("Load local JSON file"),
),
body: new Container(
child: new Center(
// Use future builder and DefaultAssetBundle to load the local JSON file
child: new FutureBuilder(
future: DefaultAssetBundle
.of(context)
.loadString('data_repo/starwars_data.json'),
builder: (context, snapshot) {
var newData = JSON.decode(snapshot.data.toString());
List<Widget> listMyWidgets(){
List<Widget> list = new List();
for( var i = 0; i < newData.length; i++){
var word = newData[i]['word']["letters"];
for( var n = 0; n < word.length; n++){
list.add(new Text(word[n]['letter']));
}
}
return list;
}
return new ListView.builder(
itemBuilder: (BuildContext context, int index) {
return new Card(
child: new Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children:[
new Column(
children:[
new Image.asset( newData[index]['image'])
]
),
new Row(
children:listMyWidgets()
)
],
),
);
},
itemCount: newData == null ? 0 : newData.length,
);
}),
),
)
));
}
}
Something similar to this:
Ah, I think what you are asking about is GridView.
class GridViewWords extends StatelessWidget {
List letters = [
"a",
"b",
"c",
"d",
"e",
"f",
"g",
"h",
"i",
"j",
"k"
];
#override
Widget build(BuildContext context) {
return new Scaffold(
body: new GridView.count(
shrinkWrap: true,
crossAxisCount: 3,
children: new List.generate(letters.length, (i)=>
new Card(
elevation: 5.0,
color: Colors.brown[500],
child: new Padding(
padding: const EdgeInsets.all(8.0),
child: new Center(child: new Text(letters[i])),
),
)),
),
);
}
}