My code:
import 'package:flutter/material.dart';
import '../services.dart';
import 'PersonCard.dart';
class PersonList extends StatefulWidget {
PersonList() {
Services.fetchPeople();
}
#override
_PersonListState createState() => new _PersonListState();
}
class _PersonListState extends State<PersonList> {
_PersonListState() {
print('Helo!?'); // Not running
}
#override
Widget build(BuildContext context) {
return new Padding(
padding: new EdgeInsets.all(10.0),
child: new ListView(
children: <Widget>[
// new PersonCard('Bob', 'Wazowski', '2 minutes ago'),
// new PersonCard('Tim', 'Alan', '5 hours ago'),
// new PersonCard('Josh', 'Applebee', 'Yesterday'),
]
)
);
}
}
Basically when my widget loads the PersonList() function runs that calls a web service with Services.fetchPeople(). Basically what I want to do is that fetchPeople() will return a Map which I can then iterate over and create PersonCard objects.
But the _PersonListState() constructor doesn't run. Am I missing something obvious?
You can use widget.people.map((p) => new PersonCard(...)) to convert data to wigets:
class PersonList extends StatefulWidget {
PersonList() {
Services.fetchPeople().then((p) => setState(() => people = p));
}
List<Person> people;
#override
_PersonListState createState() => new _PersonListState();
}
class _PersonListState extends State<PersonList> {
_PersonListState() {
print('Helo!?'); // Not running
}
#override
Widget build(BuildContext context) {
return new Padding(
padding: new EdgeInsets.all(10.0),
child: new ListView(
children: widget.people.map(
(p) => new PersonCard(p.firstName, p.lastName, p.updateTime /* or whatever that is */,
).toList(),
),
);
}
}
Related
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()}")
)
)
),
);
}
}
I'm trying to work with hero widget .. every thing working fine.. my problem the tag for hero should be unique .. for the main scaffold i can make it unique by using the id from my api .. but i can't pass this id to the second Scaffold ... it become undefined .. how i can defined it ,,,
My Code is
import 'package:flutter/material.dart';
import 'dart:async';
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:json/add.dart';
Future<List> getData() async {
String url = 'http://192.168.0.57:4000/api/contacts';
http.Response response = await http.get(url);
return json.decode(response.body);
}
List data;
void main() async {
data = await (getData());
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
routes: <String, WidgetBuilder>{
'/Add': (BuildContext context) => new Add(),
},
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
#override
State<StatefulWidget> createState() {
// TODO: implement createState
return HomePageState();
}
}
class HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
// TODO: implement build
return new MaterialApp(
title: "Test",
home: new Scaffold(
appBar: AppBar(
centerTitle: true,
title: new Text("Chat"),
),
body: new Center(
child: new ListView.builder(
itemCount: data.length,
itemBuilder: (BuildContext context, int position) {
return new ListTile(
title: new Text('${data[position]['name']}'),
subtitle: new Text('${data[position]['email']}'),
leading: new InkWell(
onTap: () {
Navigator.push(context,
MaterialPageRoute(builder: (BuildContext context) {
return HeroPage();
}));
},
child: Hero(
tag: "${data[position]['id']}",
child: new CircleAvatar(
child: new Text("${data[position]['name'][0]}"),
),
),
),
onTap: () {},
);
}),
),
),
);
}
}
class HeroPage extends StatefulWidget {
#override
State<StatefulWidget> createState() {
// TODO: implement createState
return HeroPageState();
}
}
class HeroPageState extends State<HeroPage> {
#override
Widget build(BuildContext context) {
// TODO: implement build
return new Scaffold(
appBar: AppBar(),
body: Hero(
tag: "${data[position]['id']}",
child: new Container(
color: Colors.blueAccent,
),
),
);
}
}
You can Pass the Position(Int) with help of Class Constructors.
class HeroPage extends StatefulWidget {
final int position;
final List data;
HeroPage({this.position,this.data});
#override
State<StatefulWidget> createState() {
// TODO: implement createState
return HeroPageState();
}
}
class HeroPageState extends State<HeroPage> {
#override
Widget build(BuildContext context) {
// TODO: implement build
return new Scaffold(
appBar: AppBar(),
body: Hero(
tag: "${widget.data[widget.position]['id']}",
child: new Container(
color: Colors.blueAccent,
),
),
);
}
}
Call the page like in your InkWell onTap::
Navigator.push(context,
MaterialPageRoute(builder: (BuildContext context) {
return HeroPage(position: position,data: data);
In the another page, try to wrap you hero in a ListView.builder, but the trick is only to set 1 in the itemCount param, with this, you can manipulate to show only one and get the correct tag
Just testing out flutter. The code sample below is a very simple flutter app. The problem is that I don't know how to call the setState() function inside the TestTextState class in order to change the text each time when the change button is pressed.
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Test app',
home: new Scaffold(
appBar: new AppBar(
title: new Text("Test"),
),
body: new Test(),
),
);
}
}
class Test extends StatelessWidget {
final TestText testText = new TestText();
void change() {
testText.text == "original" ? testText.set("changed") : testText.set("original");
}
#override
Widget build(BuildContext context) {
return new Column(
children: [
testText,
new RaisedButton(
child: new Text("change"),
onPressed: () => change(),
),
]
);
}
}
class TestText extends StatefulWidget {
String text = "original";
void set(String str) {
this.text = str;
}
#override
TestTextState createState() => new TestTextState();
}
class TestTextState extends State<TestText> {
#override
Widget build(BuildContext context) {
return new Text(this.widget.text);
}
}
I have approached this problem by initializing the _TestTextState as the final property of the TestText widget which allows to simply update the state when the change button is pressed. It seems like a simple solution but I'm not sure whether it's a good practice.
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Test app',
home: new Scaffold(
appBar: new AppBar(
title: new Text("Test"),
),
body: new Test(),
),
);
}
}
class Test extends StatelessWidget {
final _TestText text = new _TestText();
#override
Widget build(BuildContext context) {
return new Column(
children: [
text,
new RaisedButton(
child: new Text("change"),
onPressed: () => text.update(),
),
]
);
}
}
class TestText extends StatefulWidget {
final _TestTextState state = new _TestTextState();
void update() {
state.change();
}
#override
_TestTextState createState() => state;
}
class _TestTextState extends State<TestText> {
String text = "original";
void change() {
setState(() {
this.text = this.text == "original" ? "changed" : "original";
});
}
#override
Widget build(BuildContext context) {
return new Text(this.text);
}
}
thier is no way to do so. any how you have to convert your StatelessWidget to StatefulWidget.
Solution based on your existing code
class Test extends StatelessWidget {
final StreamController<String> streamController = StreamController<String>.broadcast();
#override
Widget build(BuildContext context) {
final TestText testText = TestText(streamController.stream);
return new Column(children: [
testText,
new RaisedButton(
child: Text("change"),
onPressed: () {
String text = testText.text == "original" ? "changed" : "original";
streamController.add(text);
},
),
]);
}
}
class TestText extends StatefulWidget {
TestText(this.stream);
final Stream<String> stream;
String text = "original";
#override
TestTextState createState() => new TestTextState();
}
class TestTextState extends State<TestText> {
#override
void initState() {
widget.stream.listen((str) {
setState(() {
widget.text = str;
});
});
super.initState();
}
#override
Widget build(BuildContext context) {
return Text(widget.text);
}
}
But it's not the best idea - to use non-final field inside Stateful Widget
P.S.
You can also use this - scoped_model
In my app i've the below pat of the code:
class _MyHomePageState extends State<MyHomePage> {
int _selected = 0;
List<Widget> makeRadios() {
List <Widget> list = new List <Widget>();
list.add(new Row(
children: <Widget>[
new Radio(value: 0, groupValue: _selected,
onChanged: (int value) {
rOnChanged(value);
}),
new Text('Radio 0'),
],
));
list.add(new Row(
children: <Widget>[
new Radio(value: 1, groupValue: _selected,
onChanged: (int value) {
rOnChanged(value);
}),
new Text('Radio 1'),
],
));
return list;
};
void rOnChanged(int value){
this.setState(() {
_selected = value;
});
print("value: $value");
this._bodyHeight = (value == 1) ? 65.0 : 0.0;
}
// and lots more lines
}
I want to split this part and move t to another .dart file in order to reduce the main.dart file, so my two files became like:
library myLib;
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.deepOrange,
),
home: new MyHomePage(title: 'Flutter App Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
// other lines
}
And the other file as:
part of myLib;
int _selected = 0;
List<Widget> makeRadios() {
List <Widget> list = new List <Widget>();
list.add(new Row(
children: <Widget>[
new Radio(value: 0, groupValue: _selected,
onChanged: (int value) {
rOnChanged(value);
}),
new Text('Radio 0'),
],
));
list.add(new Row(
children: <Widget>[
new Radio(value: 1, groupValue: _selected,
onChanged: (int value) {
rOnChanged(value);
}),
new Text('Radio 1'),
],
));
return list;
};
void rOnChanged(int value){
this.setState(() {
_selected = value;
});
print("value: $value");
this._bodyHeight = (value == 1) ? 65.0 : 0.0;
}
But it did not work, and the second file became full of errors!
What is the best way to split/scale the Flutter
Don't separate State<T> from it's StatefulWidget.
If you want to extract that widget to it's own file, move both parts entirely.
So you can have
// lib/main.dart
import 'package:flutter/material.dart';
import 'package:myapp/src/myapp.dart';
void main() => runApp(new MyApp());
and in lib/src/myapp.dart
// lib/src/myapp.dart
import 'package:flutter/material.dart';
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => new _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return new Container();
}
}
After searching and reading the given answers and comments, I think the best way to achieve scalability, is to create separate file for each Widget that include the layout and all related function, then add it for the main file/app, for example I wrote the below code to draw a Switch, read its value and save it in the shared preferences:
file name widget.dart
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
class Switchy extends StatefulWidget{
Switchy({Key key}) : super(key: key);
#override
State<StatefulWidget> createState() => new _SwitchyState();
}
class _SwitchyState extends State<Switchy> {
var _value = true;
void onchange(bool value) {
setState(() {
_value = value;
_savePref(value);
});
}
_savePref(value) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
bool use = prefs.getBool('use');
await prefs.setBool('use', value);
print('value changed from $use to $value');
}
#override
Widget build(BuildContext context) {
return
new Card(
child: new Container(
child: new Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Text("Enable/Disable the app in the background"),
new Switch(value: _value, onChanged: (bool value) => onchange(value)),
],
),
),
);
}
}
Then, in the main.dart file I used it as:
import 'widgets.dart';
// ..
children: <Widget>[
new Switchy();
//...
]
I want show a popup at touch Coordinates. I am using Stack and Positioned widgets to place the popup.
You can add a GestureDetector as parent of stack, and register onTapDownDetails listener. This should call your listener on every tapdown event, with global offset of the tap in TapDownDetails parameter of the your listener.
Here is sample code demonstrating the same.
import 'package:flutter/material.dart';
void main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
home: new MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
MyHomePageState createState() => new MyHomePageState();
}
class MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Popup Demo'),
),
body: new MyWidget());
}
}
class MyWidget extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return new MyWidgetState();
}
}
class MyWidgetState extends State<MyWidget> {
double posx = 100.0;
double posy = 100.0;
void onTapDown(BuildContext context, TapDownDetails details) {
print('${details.globalPosition}');
final RenderBox box = context.findRenderObject();
final Offset localOffset = box.globalToLocal(details.globalPosition);
setState(() {
posx = localOffset.dx;
posy = localOffset.dy;
});
}
#override
Widget build(BuildContext context) {
return new GestureDetector(
onTapDown: (TapDownDetails details) => onTapDown(context, details),
child: new Stack(fit: StackFit.expand, children: <Widget>[
// Hack to expand stack to fill all the space. There must be a better
// way to do it.
new Container(color: Colors.white),
new Positioned(
child: new Text('hello'),
left: posx,
top: posy,
)
]),
);
}
}
You can simply use a Listener as the parent of your Stack and listen to it's onPointerDown event like so:
Listener(
onPointerDown: (event) {
// use event.localPosition.dx or event.localPosition.dy
},
child: Stack(
children: [
],
),
)