How to assign <List<Data>> to list variable? - dart

How to display one by one data using this DB function?
Future<List<Data>> display() async {
//final Database db = await database;
var db = await db1;
final List<Map<String, dynamic>> maps = await db.query('syncTable');
return List.generate(maps.length, (i) {
return Data(
syn_TableName: maps[i]['syn_TableName'],
syn_ChangeSequence: maps[i]['syn_ChangeSequence'],
);
});
}

You can use the FutureBuilder to consume your display() method. Then inside the FutureBuilder you can use AsyncSnapshot.data to get your List of Dataelements.
In the next step you use can call List.map() to map your Data to widgets. In this example I use the ListTile to display:
snapshot.data.map((data) {
return ListTile(
title: Text(data.syn_TableName),
subtitle: Text(data.syn_ChangeSequence),
);
}).toList(),
Here a minimal working example which you can try out:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: FutureBuilder<List<Data>>(
initialData: [],
future: display(),
builder: (context, snapshot) {
return ListView(
children: snapshot.data.map((data) {
return ListTile(
title: Text(data.syn_TableName),
subtitle: Text(data.syn_ChangeSequence),
);
}).toList(),
);
}),
),
);
}
Future<List<Data>> display() async {
return List.generate(15, (i) {
return Data(
syn_TableName: 'syn_TableName $i',
syn_ChangeSequence: 'syn_ChangeSequence $i',
);
});
}
}
class Data {
final String syn_TableName;
final String syn_ChangeSequence;
Data({this.syn_ChangeSequence, this.syn_TableName});
}

Related

How to delete GridView item in flutter?

I'm trying to dynamically delete simple grid item on long press;
I've tried the most obvious way: created a list of grid data, and called setState on addition or deletion of the item.
UPD: Items works properly in the list, since it's initialisation loop moved to initState() method (just as #jnblanchard said in his comment), and don't generate new items at every build() call, but deletion is still doesn't work.
If it has more items, than can fit the screen, it deletes last row, (when enough items deleted), otherwise the following exception is thrown:
I/flutter (28074): The following assertion was thrown during performLayout():
I/flutter (28074): SliverGeometry is not valid: The "maxPaintExtent" is less than the "paintExtent".
I/flutter (28074): The maxPaintExtent is 540.0, but the paintExtent is 599.3. By definition, a sliver can't paint more
I/flutter (28074): than the maximum that it can paint!
My test code now:
main class
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:options_x_ray_informer/prototyping/TestTile.dart';
class Prototype extends StatefulWidget{
#override
_PrototypeState createState() => _PrototypeState();
}
class _PrototypeState extends State<Prototype> {
//list of grid data
List<Widget> gridItemsList = [];
#override
void initState(){
super.initState();
//----filling the list----
for(int i =0; i<10; i++){
gridItemsList.add(
TestTile(i, (){
//adding callback for long tap
delete(i);
})
);
}
}
#override
Widget build(BuildContext context) {
//----building the app----
return Scaffold(
appBar: AppBar(
title: Text("Prototype"),
actions: <Widget>[
IconButton(
icon: Icon(Icons.add),
onPressed: () {
int index = gridItemsList.length+1;
add(
new TestTile(index, (){
delete(index);
})
);
},
),
]
),
body: GridView(
gridDelegate: new SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2),
children: gridItemsList
)
);
}
///method for adding the items
void add(Widget toAdd){
setState(() {
TestTile tile = toAdd as TestTile;
gridItemsList.add(toAdd);
print("tile number#${tile.index} added");
});
}
///method for deleting the items
void delete(int index){
setState(() {
gridItemsList.removeAt(index);
print("tile number#$index is deleted");
});
}
}
and separate widget class for grid items
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
class TestTile extends StatelessWidget{
int _index;
var _callback;
TestTile(this._index, this._callback);
get index => _index;
#override
Widget build(BuildContext context) {
return GridTile(
child: Card(
child: InkResponse(
onLongPress: _callback,
child: Center(
child:Text("data#$_index")
)
)
),
);
}
}
How can I delete an item from grid view?
p.s. the provided code is just my attempts of solving the problem - you can offer another way, if you want!
I wrote this up from the example app, it has a few things that you may find useful. Notably I abstract the list data-structure by holding the length of the list inside a stateful widget. I wrote this with a ListView but I think you could change that to a GridView without any hiccups.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.indigo,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
#override
Widget build(BuildContext context) {
return Scaffold(appBar: AppBar(
title: Text("Owl"),
actions: <Widget>[IconButton(icon: Icon(Icons.remove), onPressed: () => this.setState(() => _counter > 1 ? _counter-- : _counter = 0)), IconButton(icon: Icon(Icons.add), onPressed: () => this.setState(() => _counter++))],
),
body: ListView.builder(itemExtent: 50, itemCount: _counter, itemBuilder: (context, index) => Text(index.toString(), textAlign: TextAlign.center, style: Theme.of(context).textTheme.title))
);
}
}
Finally I've got what I wanted.
I'll leave it here for someone who might have the same problem :)
Main class:
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:options_x_ray_informer/prototyping/TestTile.dart';
class Prototype extends StatefulWidget{
#override
_PrototypeState createState() => _PrototypeState();
}
class _PrototypeState extends State<Prototype> {
//list of some data
List<Person> partyInviteList = [];
_PrototypeState(){
//filling the list
for(int i=0; i<5; i++){
partyInviteList.add(Person.generateRandomPerson());
}
print("Person ${partyInviteList.toString()}");
}
#override
Widget build(BuildContext context) {
//----building the app----
return Scaffold(
appBar: AppBar(
title: Text("Prototype"),
actions: <Widget>[
IconButton(
icon: Icon(Icons.add),
//generating an item on tap
onPressed: () {
setState(() {
partyInviteList.add(Person.generateRandomPerson());
});
},
),
]
),
body: GridView.count(
crossAxisCount: 2,
children: List.generate(partyInviteList.length, (index) {
//generating tiles with people from list
return TestTile(
partyInviteList[index], (){
setState(() {
print("person ${partyInviteList[index]} is deleted");
partyInviteList.remove(partyInviteList[index]);
});
}
);
})
)
);
}
}
///person class
class Person{
Person(this.firstName, this.lastName);
static List<String> _aviableNames = ["Bob", "Alise", "Sasha"];
static List<String> _aviableLastNames = ["Green", "Simpson", "Stain"];
String firstName;
String lastName;
///method that returns random person
static Person generateRandomPerson(){
Random rand = new Random();
String randomFirstName = _aviableNames[rand.nextInt(3)];
String randomLastName = _aviableLastNames[rand.nextInt(3)];
return Person(randomFirstName, randomLastName);
}
#override
String toString() {
return "$firstName $lastName";
}
}
Support class:
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:options_x_ray_informer/prototyping/Prototype.dart';
class TestTile extends StatelessWidget{
final Person person;
var _callback;
TestTile(this.person, this._callback);
#override
Widget build(BuildContext context) {
return GridTile(
child: Card(
child: InkResponse(
onLongPress: _callback,
child: Center(
child:Text("${person.toString()}")
)
)
),
);
}
}

Bloc cannot return data in the dialog

I am developing a simple todo app using flutter with BloC pattern.
It has a ui to display TodoDetails.
When a user click a button, it show a new SimpleDialog.
I want to show some Tag list in the SimpleDialog like:
class AddEditTodoPage extends StatefulWidget {
final TodoRepository todoRepository;
final TagRepository tagRepository;
final Todo todo;
final SaveTodoBloc bloc;
AddEditTodoPage({this.todoRepository, this.tagRepository, this.todo})
: bloc = SaveTodoBloc(
todoRepository: todoRepository,
tagRepository: tagRepository,
todo: todo);
#override
State<StatefulWidget> createState() => _AddEditTodoPageState(todo: todo);
}
class _AddEditTodoPageState extends State<AddEditTodoPage> {
final Todo todo;
_AddEditTodoPageState({this.todo});
#override
Widget build(BuildContext context) {
return Center(
child: StreamBuilder<Tag>(
stream: widget.bloc.tag,
builder: (context, snapshot) {
final tag = snapshot.data;
return OutlineButton(
onPressed: () async {
final selectedTag = await showDialog<TagSelection>(
context: context,
builder: (context) => _showTagSelectDialog(context),
);
},
);
}},
);
}
_showTagSelectDialog(BuildContext context) => SimpleDialog(
title: Text("Select a Tag or create a new one"),
children: <Widget>[
StreamBuilder<List<Tag>>(
stream: widget.bloc.tags,
builder: (context, snapshot) {
final tagList = snapshot.data;
if (tagList == null || tagList.isEmpty) {
// This is always 'null'!!!
return SizedBox();
} else {
return ListView(
children: tagList.map(_buildTagName).toList(),
);
}
}),
],
);
Widget _buildTagName(Tag tag) => Text(tag.name);
}
So my bloc is getting the TagList like:
class SaveTodoBloc {
final TodoRepository todoRepository;
final TagRepository tagRepository;
final Todo todo;
SaveTodoBloc({this.todoRepository, this.tagRepository, this.todo}) {
if (tagRepository != null) {
_getTags();
}
}
final _getTagsSubject = PublishSubject<List<Tag>>();
Stream<List<Tag>> get tags => _getTagsSubject.stream;
Future<Null> _getTags() async {
await tagRepository.getAll().then((list) {
_getTagsSubject.add(list);
print("[SaveTodoBloc][JOS] _getTags - $list"); // It resturns correct list of Tags.
});
}
}
When I check the log, the bloc logic returns correct list of Tags.
But when I show the Dialog, it doesn't have list of tags.
The list is null.

How do I make RefreshIndicator disappear?

I have this code that has the parent widget Homepage and the child widget CountryList. In CountryList, I have created a function that uses an API to get a list of countries. I felt like enabling a RefreshIndicator in the app, so I had to modify the Homepage widget and add GlobalKey to access getCountryData() function of CountryList widget. The RefreshIndicator has done its job well. But the problem now is that when I pull and use the RefreshIndicator in the app, the getCountryData() function is called, but even after showing all data in the list, the circular spinner doesn't go (shown in the screenshot).
So, could anyone please suggest me a way to make the spinner go?
The code of main.dart containing Homepage widget is given below:
import 'package:flutter/material.dart';
import 'country_list.dart';
GlobalKey<dynamic> globalKey = GlobalKey();
void main() => runApp(MaterialApp(home: Homepage()));
class Homepage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("List of countries"), actions: <Widget>[
IconButton(icon: Icon(Icons.favorite), onPressed: (){},)
],),
body: RefreshIndicator(child: CountryList(key:globalKey), onRefresh: (){globalKey.currentState.getCountryData();},),
);
}
}
And the code of country_list.dart containing CountryList widget is:
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'package:flutter_svg/flutter_svg.dart';
class CountryList extends StatefulWidget {
CountryList({Key key}) : super(key: key);
#override
_CountryListState createState() => _CountryListState();
}
class _CountryListState extends State<CountryList> {
List<dynamic> _countryData;
bool _loading = false;
#override
void initState() {
// TODO: implement initState
super.initState();
this.getCountryData();
}
Future<String> getCountryData() async {
setState(() {
_loading = true;
});
var response =
await http.get(Uri.encodeFull("https://restcountries.eu/rest/v2/all"));
var decodedResponse = json.decode(response.body);
setState(() {
_countryData = decodedResponse;
_loading = false;
});
}
#override
Widget build(BuildContext context) {
return _loading?Center(child: Column(mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[CircularProgressIndicator(), Padding(padding: EdgeInsets.all(5.0),), Text("Loading data...", style: TextStyle(fontSize: 20.0),)],)):ListView.builder(
itemCount: _countryData.length,
itemBuilder: (BuildContext context, int index) {
return Card(
child: ListTile(
leading: SvgPicture.network(_countryData[index]['flag'], width: 60.0,),
title: Text(_countryData[index]['name']),
trailing: IconButton(
icon: Icon(Icons.favorite_border),
onPressed: () {},
),
),
);
},
);
}
}
You need to add return here:
Future<String> getCountryData() async {
setState(() {
_loading = true;
});
var response =
await http.get(Uri.encodeFull("https://restcountries.eu/rest/v2/all"));
var decodedResponse = json.decode(response.body);
setState(() {
_countryData = decodedResponse;
_loading = false;
});
return 'success';
}
and here:
body: RefreshIndicator(
child: CountryList(key: globalKey),
onRefresh: () {
return globalKey.currentState.getCountryData();
},
),
The onRefresh callback is called. The callback is expected to update the scrollable's contents and then complete the Future it returns. The refresh indicator disappears after the callback's Future has completed, I think you should return Future<String> from getCountryData.

Passing data between pages with bottom navigation bar in flutter

I have an app that routes to a "mainapp" page after logging in. This app contains a bottom navigation bar which displays pages of the corresponding pressed icon. I want to pass data of type Map<String, dynamic> to these pages but I am having trouble. This map is generated from a function that fetches the data from a server, saves it to shared preferences, then loads the shared preferences and returns it as a map (all contained in getData()). I want to pass this map around so I don't have to load shared preferences each time, but will also update this map along with shared preferences when needed( possibly an action on one of the pages).
class MainApp extends StatefulWidget {
#override
_MainAppState createState() => _MainAppState();
}
class _MainAppState extends State<MainApp> {
Map<String, dynamic> Data;
StartFunc() async {
Data = await getData();
setState(() {});
}
#override
void initState() {
StartFunc();
super.initState();
}
var _pages = [
PageOne(Data:Data),
PageTwo(),
PageThree(),
PageFour(),
PageFive(),
];
int _currentIndex = 0;
onTabTapped(int index) {
setState(() {
_currentIndex = index;
});
}
#override
Widget build(BuildContext context) {
return _currentIndex == 2
? PageTwo()
: Scaffold(
body: _pages[_currentIndex],
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
items: [
BottomNavigationBarItem(
icon: Icon(Icons.library_books), title: Text('')),
BottomNavigationBarItem(
icon: Icon(Icons.notifications), title: Text('')),
BottomNavigationBarItem(
icon: Icon(Icons.add_circle_outline), title: Text('')),
BottomNavigationBarItem(
icon: Icon(Icons.mail), title: Text('')),
BottomNavigationBarItem(
icon: Icon(Icons.person), title: Text('')),
],
onTap: onTabTapped,
currentIndex: _currentIndex,
),
);
}
}
I'm getting an error saying Only static members can be accessed in initializers. I was wondering if inherited widgets or other design patterns such as scoped model and BLoC can help but not sure if that's the right way to go. I'm also not sure how I would start implementing them in this case.
There are two problems in your code:
using an async method in the body of initState()
see here for details
using instance data in an initializer
see here for details
What follow is a very basic rewrite of your code, with minimal corrections.
The data map is loaded from a mocked backend, updated inside PageOne and printed to console in PageTwo onTap callback.
Please note that I've changed instance variable Data to data to be compliant with Effective Dart guidelines.
Note that the gist does not properly addresses the synchronization of the backend service with the shared preferences: this is something that have probably to be accounted in the final product.
I just commented what it is necessary to get your code works:
if the complexity of your system and the relations with external API start growing it could be worth considering a Bloc architecture.
import 'package:flutter/material.dart';
void main() => runApp(new MainApp());
// Mock up of an async backend service
Future<Map<String, dynamic>> getData() async {
return Future.delayed(Duration(seconds: 1), () => {'prop1': 'value1'});
}
class PageOne extends StatelessWidget {
final Map<String, dynamic> data;
PageOne({Key key, this.data}) : super(key: key);
#override
Widget build(BuildContext context) {
return Center(
child: RaisedButton(
child: const Text('update preferences'),
onPressed: () {
data['prop2'] = 'value2';
},
),
);
}
}
class PageTwo extends StatelessWidget {
final Map<String, dynamic> data;
PageTwo({Key key, this.data}) : super(key: key);
#override
Widget build(BuildContext context) {
return Center(
child: RaisedButton(
child: const Text('Got It!'),
onPressed: () {
print("data is now: [$data]");
},
),
);
}
}
class MainApp extends StatefulWidget {
#override
_MainAppState createState() => _MainAppState();
}
class _MainAppState extends State<MainApp> {
//Map<String, dynamic> Data;
Map<String, dynamic> data;
/*
StartFunc() async {
Data = await getData();
setState(() {});
}
*/
#override
void initState() {
//StartFunc();
super.initState();
getData().then((values) {
setState(() {
data = values;
});
});
}
/*
PageOne(data:data) is an invalid value for an initializer:
there is no way to access this at this point.
Initializers are executed before the constructor,
but this is only allowed to be accessed after the call
to the super constructor.
*/
/*
var _pages = [
PageOne(data:data),
PageTwo(),
];
*/
Widget getPage(int index) {
switch (index){
case 0:
return PageOne(data:data);
break;
case 1:
return PageTwo(data:data);
break;
default:
return PageOne();
break;
}
}
int _currentIndex = 0;
onTabTapped(int index) {
setState(() {
_currentIndex = index;
});
}
#override
Widget build(BuildContext context) {
/*
return _currentIndex == 2
? PageTwo()
: Scaffold(
I use a MaterialApp because of material widgets (RaisedButton)
It is not mandatory, but it is mainstream in flutter
*/
return MaterialApp(
title: 'My App',
home: Scaffold(
appBar: AppBar(title: Text("My App Bar")),
body: getPage(_currentIndex),
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
items: [
BottomNavigationBarItem(
icon: Icon(Icons.first_page), title: Text('')),
BottomNavigationBarItem(
icon: Icon(Icons.last_page), title: Text('')),
],
onTap: onTabTapped,
currentIndex: _currentIndex,
),
));
}
}

Flutter - Data from json api to be listed in drop down list

I am trying to fetch the data from json api and to be listed in the drop down list but i am hitting with The method 'map' was called on null error.
import "package:flutter/material.dart";
import 'dart:async';
import 'dart:convert';
import 'package:http/http.dart' as http;
void main() => runApp(MaterialApp(
title: "Hospital Management",
home: MyApp(),
));
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String _mySelection;
final String url = "http://webmyls.com/php/getdata.php";
List data;
Future<String> getSWData() async {
var res = await http
.get(Uri.encodeFull(url), headers: {"Accept": "application/json"});
var resBody = json.decode(res.body);
setState(() {
data = resBody;
});
print(resBody);
return "Sucess";
}
#override
void initState() {
super.initState();
this.getSWData();
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: AppBar(
title: Text("Hospital Management"),
),
body: new Center(
child: new DropdownButton(
items: data.map((item) {
return new DropdownMenuItem(
child: new Text(item['item_name']),
value: item['id'].toString(),
);
}).toList(),
onChanged: (newVal) {
setState(() {
_mySelection = newVal;
});
},
value: _mySelection,
),
),
);
}
}
The error from the debug consol
flutter: The method 'map' was called on null.
flutter: Receiver: null
flutter: Tried calling: map<DropdownMenuItem<String>>(Closure:
(dynamic) => DropdownMenuItem<String>)
flutter:
flutter: When the exception was thrown, this was the stack:
flutter: #0 Object.noSuchMethod
(dart:core/runtime/libobject_patch.dart:48:5)
I am expecting to show the list of data from the json api in the drop down menu. I am beginner to dart and flutter. Help me out to solve the error.
data is not initialized. It is set on getSWData which is async.
A possible solution could be to have data as emptyList as an initial value.
List data = List();
If you want to display DropdownButton based on data, you can use FutureBuilder and show loader or something till data comes from getSWData
Your Api http://webmyls.com/php/getdata.php returns a list not a map. Hence, it cannot have direct property advisor_report. Consider removing it.
Data should be changed inside setState.
You never called getSWData()
Below is more improved example.
import "package:flutter/material.dart";
import 'dart:async';
import 'dart:convert';
import 'package:http/http.dart' as http;
void main() => runApp(MaterialApp(
title: "Hospital Management",
home: MyApp(),
));
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String _mySelection;
final String url = "http://webmyls.com/php/getdata.php";
List data;
Future<String> getSWData() async {
var res = await http
.get(Uri.encodeFull(url), headers: {"Accept": "application/json"});
var resBody = json.decode(res.body);
setState(() {
data = resBody;
});
print(resBody);
return "Sucess";
}
#override
void initState() {
super.initState();
this.getSWData();
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: AppBar(
title: Text("Hospital Management"),
),
body: new Center(
child: new DropdownButton(
items: data.map((item) {
return new DropdownMenuItem(
child: new Text(item['item_name']),
value: item['id'].toString(),
);
}).toList(),
onChanged: (newVal) {
setState(() {
_mySelection = newVal;
});
},
value: _mySelection,
),
),
);
}
}

Resources