Related
I started studying Flutter these days and I made the app of this tutorial smoothly.
I managed to create a button that switch from list and card visualization, an icon that removes a word and instead of showing infinite words now shows only 20 words. But now I wanted to create another screen via named routes and on this screen you can edit the name you clicked on screen1, for example:
But I'm having a lot of trouble trying to do this. I managed to create the screen2, the textform and the routes but the part of changing the name is still giving me little headaches xD
Could someone help me please? This is the code:
import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Startup Names',
initialRoute: '/',
routes: {
'/': (context) => const RandomWords(),
EditScreen.routeName: (context) => EditScreen(),
},
);
}
}
class RandomWords extends StatefulWidget {
const RandomWords({super.key});
#override
State<RandomWords> createState() => _RandomWordsState();
}
class _RandomWordsState extends State<RandomWords> {
final _suggestions = <WordPair>[];
final _saved = <WordPair>{};
final _biggerFont = const TextStyle(fontSize: 20);
String selected = '';
bool isList = true;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Startup Names'),
actions: [
IconButton(
icon: const Icon(Icons.bookmark_border_sharp),
onPressed: _pushSaved,
tooltip: 'Favorites',
),
IconButton(
icon:
isList ? const Icon(Icons.grid_view) : const Icon(Icons.list),
onPressed: () {
setState(() {
isList = !isList;
});
},
tooltip: isList ? 'Card mode' : 'List mode',
),
],
),
body: isList ? lista() : cards());
}
Widget lista() {
return ListView.builder(
padding: const EdgeInsets.all(16.0),
itemCount: 40,
itemBuilder: (context, i) {
if (i.isOdd) return const Divider();
final index = i ~/ 2;
if (index >= _suggestions.length) {
_suggestions.addAll(generateWordPairs().take(10));
}
return favorites(_suggestions[index], index);
},
);
}
Widget cards() {
return GridView.builder(
padding: const EdgeInsets.all(16.0),
itemCount: 20,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 5,
mainAxisSpacing: 5,
mainAxisExtent: 100),
itemBuilder: (context, i) {
if (i >= _suggestions.length) {
_suggestions.addAll(generateWordPairs().take(10));
}
return Card(child: favorites(_suggestions[i], i));
});
}
Widget favorites(WordPair pair, int index) {
final alreadySaved = _saved.contains(pair);
return ListTile(
title: Text(
pair.asPascalCase,
style: _biggerFont,
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: Icon(alreadySaved ? Icons.favorite : Icons.favorite_border),
color: alreadySaved ? Colors.red : null,
tooltip: alreadySaved ? 'Remove favorite' : 'Favorite',
onPressed: () {
setState(() {
if (alreadySaved) {
_saved.remove(pair);
} else {
_saved.add(pair);
}
});
},
),
IconButton(
icon: const Icon(Icons.delete),
color: Colors.black87,
tooltip: 'Remove from list',
onPressed: () {
setState(() {
_suggestions.remove(pair);
_saved.remove(pair);
});
},
),
],
),
onTap: () {
selected = pair.asPascalCase;
Navigator.pushNamed(context, '/second',
arguments: Argumentos(index, selected));
});
}
void _pushSaved() {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (context) {
final tiles = _saved.map(
(pair) {
return ListTile(
title: Text(
pair.asPascalCase,
style: _biggerFont,
),
);
},
);
final divided = tiles.isNotEmpty
? ListTile.divideTiles(
context: context,
tiles: tiles,
).toList()
: <Widget>[];
return Scaffold(
appBar: AppBar(
title: const Text('Favorites'),
),
body: ListView(children: divided),
);
},
),
);
}
}
class EditScreen extends StatefulWidget {
static const routeName = '/second';
const EditScreen({super.key});
#override
State<EditScreen> createState() => _EditScreenState();
}
class _EditScreenState extends State<EditScreen> {
final first = TextEditingController();
final second = TextEditingController();
#override
Widget build(BuildContext context) {
final argumentos = ModalRoute.of(context)?.settings.arguments as Argumentos;
String selected = argumentos.name;
return Scaffold(
appBar: AppBar(
title: Text('Edit word'),
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text("What's the new name?",
style: TextStyle(fontSize: 30)),
const SizedBox(height: 30),
Padding(
padding:
const EdgeInsets.symmetric(horizontal: 100, vertical: 16),
child: TextFormField(
controller: first,
decoration: const InputDecoration(
border: OutlineInputBorder(),
labelText: 'First part of the name',
),
),
),
Padding(
padding:
const EdgeInsets.symmetric(horizontal: 100, vertical: 16),
child: TextFormField(
controller: second,
decoration: const InputDecoration(
border: OutlineInputBorder(),
labelText: 'Second part of the name',
),
),
),
const SizedBox(height: 20),
ElevatedButton(
child: const Text('Change it'),
onPressed: () {
selected = (WordPair(first.text, second.text)).toString();
Navigator.pushNamed(context, '/');
},
),
],
),
));
}
}
class Argumentos {
final int id;
final String name;
Argumentos(this.id, this.name);
}
You can use .then() and update value.
Exp :
Navigator.pushNamed(context, '/second',
arguments: Argumentos(index, selected)).then((value) {
setState(() { // update ui
// update value here;
});});
then() : https://api.flutter.dev/flutter/dart-async/Future/then.html
I need to refresh my FutureBuilder => Listview.builder => Listtile, but i found no work around. when i do a hot reload, it updates, so there must be a way to reload whole classes? Its enIt would also be enough if I could only update the Listview.builder/FutureBuilder/ListTile. My code of my .dart:
import 'dart:async' show Future;
import 'package:ListGet/QR_Code/QR_Code_main.dart';
import 'package:ListGet/global/classes/routing_consting.dart';
import 'package:ListGet/localization/localizatzion_constants.dart';
import 'package:flutter/services.dart' show rootBundle;
import 'dart:convert';
import 'package:ListGet/global/global.dart' as globals;
import 'package:ListGet/global/classes/OverviewProduct.dart';
import 'package:flutter/material.dart';
import '../../main.dart';
class OverViewList extends StatefulWidget {
OverViewList({Key key}) : super(key: key);
#override
_OverViewListState createState() => _OverViewListState();
}
class _OverViewListState extends State<OverViewList> {
String data = "";
#override
Widget build(BuildContext context) {
return Scaffold(
body: RefreshIndicator(
onRefresh: _loadproductsoverview,
child: Container(
child: Column(
children: [
Expanded(
child: Container(
child: FutureBuilder(
builder: (context, snapshot) {
var showData = json.decode(snapshot.data.toString());
globals.jsongetitemlist = showData;
if (snapshot.hasData) {
return ListView.builder(
itemCount: showData.length,
itemBuilder: (BuildContext context, int index) {
return Padding(
padding: EdgeInsets.fromLTRB(12, 0, 12, 0),
child: GestureDetector(
onLongPress: () {
loadproductsoverview(index).then(
(value) => print(globals.productName));
},
onDoubleTap: () {
print("2");
},
onTap: () {
print("3");
},
child: Card(
child: ListTile(
contentPadding:
EdgeInsets.fromLTRB(8, 5, 5, 5),
title: Padding(
padding: const EdgeInsets.fromLTRB(
0, 0, 0, 10),
child:
Text(showData[index]["Product_Name"]),
),
subtitle: Text(getLang(context, "stock") +
" : " +
showData[index]["Stock"] +
"\n" +
getLang(context, "expiry_date") +
" " +
showData[index]["Expiry_date"]),
),
),
),
);
},
);
}
return CircularProgressIndicator();
},
future: DefaultAssetBundle.of(context).loadString(
"lib/global/Overview_Products/Overview_Product.json"),
),
),
),
Container(
color: "dark" == "dark"
? globals.darkThemeslide4
: globals.whiteThemeslide7,
height: 46,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Padding(
padding: const EdgeInsets.fromLTRB(4, 5, 15, 5),
child: IconButton(
onPressed: () => _scan(),
icon: Icon(Icons.qr_code_scanner_rounded),
iconSize: 30,
tooltip: "Scan your Product",
),
),
Padding(
padding: const EdgeInsets.fromLTRB(4, 5, 15, 5),
child: IconButton(
onPressed: () {
Navigator.pushNamed(context, InterfaceViewRoute,
arguments: "");
print(globals.themeswitch);
main();
},
icon: Icon(Icons.edit),
iconSize: 30,
tooltip: "Enter your Product by your self",
),
),
],
),
),
],
),
),
),
);
}
Future<String> _loadproductsoverview() async {
print("1");
return await rootBundle
.loadString('lib/global/Overview_Products/Overview_Product.json');
}
Future loadproductsoverview(index) async {
String jsonString = await _loadproductsoverview();
final jsonResponse = json.decode(jsonString);
OverviewProducts student =
new OverviewProducts.fromJson(jsonResponse[index]);
globals.productName = student.productName;
}
// ignore: missing_return
Future<String> _scan() async {
await FlutterBarcodeScanner.scanBarcode(
"#000000", "Back", true, ScanMode.BARCODE)
.then((value) => setState(() => data = value));
if (data != "-1") {
Navigator.pushNamed(context, InterfaceViewRoute, arguments: data);
}
}
}
test() {}
when i swipe from top to bottom, all entrys should be updated, out of the json.
tried also this :
class _OverViewListState extends State<OverViewList> {
String data = "";
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: Column(
children: [
Expanded(
child: Container(
child: FutureBuilder(
builder: (context, snapshot) {
var showData = json.decode(snapshot.data.toString());
globals.jsongetitemlist = showData;
if (snapshot.hasData) {
return RefreshIndicator(
onRefresh: _loadproductsoverview,
child: ListView.builder(
itemCount: showData.length,
itemBuilder: (BuildContext context, int index) {
return Padding(
padding: EdgeInsets.fromLTRB(12, 0, 12, 0),
child: GestureDetector(
onLongPress: () {
loadproductsoverview(index).then(
(value) => print(globals.productName));
},
onDoubleTap: () {
print("2");
},
onTap: () {
print("3");
},
child: Card(
child: ListTile(
contentPadding:
EdgeInsets.fromLTRB(8, 5, 5, 5),
title: Padding(
padding: const EdgeInsets.fromLTRB(
0, 0, 0, 10),
child:
Text(showData[index]["Product_Name"]),
),
subtitle: Text(getLang(context, "stock") +
" : " +
showData[index]["Stock"] +
"\n" +
getLang(context, "expiry_date") +
" " +
showData[index]["Expiry_date"]),
),
),
),
);
},
),
);
}
return CircularProgressIndicator();
},
future: DefaultAssetBundle.of(context).loadString(
"lib/global/Overview_Products/Overview_Product.json"),
),
),
),
Container(
color: "dark" == "dark"
? globals.darkThemeslide4
: globals.whiteThemeslide7,
height: 46,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Padding(
padding: const EdgeInsets.fromLTRB(4, 5, 15, 5),
child: IconButton(
onPressed: () => _scan(),
icon: Icon(Icons.qr_code_scanner_rounded),
iconSize: 30,
tooltip: getLang(context, "tooltip_qrcode"),
),
),
Padding(
padding: const EdgeInsets.fromLTRB(4, 5, 15, 5),
child: IconButton(
onPressed: () {
Navigator.pushNamed(context, InterfaceViewRoute,
arguments: "");
print(globals.themeswitch);
main();
},
icon: Icon(Icons.edit),
iconSize: 30,
tooltip: getLang(context, "tooltip_pencil"),
),
),
],
),
),
],
),
),
);
}
Future<String> _loadproductsoverview() async {
print("1");
setState(() {}); // <======= Add here
return await rootBundle
.loadString('lib/global/Overview_Products/Overview_Product.json');
}
Future loadproductsoverview(index) async {
String jsonString = await rootBundle.loadString('lib/global/Overview_Products/Overview_Product.json'); //<== dont call the function here
final jsonResponse = json.decode(jsonString);
OverviewProducts student =
new OverviewProducts.fromJson(jsonResponse[index]);
globals.productName = student.productName;
}
Please add a setState in _loadproductsoverview() method. This should do the trick;
Method loadString has a property cache, which is true by default. Set it to false and the file will be re-read.
https://api.flutter.dev/flutter/services/AssetBundle/loadString.html
And also swap FutureBuilder and RefreshIndicator. Then you can control state via using setState.
i tried the reorderables package https://pub.dev/packages/reorderables
I successed to move my dashboard blocs but when I restart app, my moves are removed.
So the solution can only be a sharedpref solution.
But I dont found how to save this information
I tried to save and load newIndex but without success
I tried to save and load List _tiles; but sharedpref can't save List
Here is my code example
List<Widget> _tiles;
void _onReorder(int oldIndex, int newIndex) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
setState(() {
Widget row = _tiles.removeAt(oldIndex);
_tiles.insert(newIndex, row);
//prefs.setListWidget('indexList', _tiles); not working
// prefs.setInt('index', newIndex ); not working
});
}
#override
void initState() {
super.initState();
_tiles = <Widget>[
//my widget1
//my widget2
//my widget3
//my widget4
//my widget5
//my widget6
//my widget7
//my widget8
//my widget9
]
}
#override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
ReorderableWrap(
spacing: 0.0,
runSpacing:0,
maxMainAxisCount: 3,
minMainAxisCount: 3,
padding: const EdgeInsets.all(5),
children:_tiles,
onReorder: _onReorder,
onNoReorder: (int index) {
//this callback is optional
debugPrint('${DateTime.now().toString().substring(5, 22)} reorder cancelled. index:$index');
},
onReorderStarted: (int index) {
//this callback is optional
debugPrint('${DateTime.now().toString().substring(5, 22)} reorder started: index:$index');
}
)
]
);
}
}
edit : here is Widget 1. other widget are same
new Container (
width: SizeConfig.safeBlockHorizontal * 32,
height: 160,
child :
new Card(
elevation: 8,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
color: Colors.white,
child:
Ink(
child: InkResponse(
splashFactory: InkRipple.splashFactory,
radius: 100,
onTap: () {
},
child:
Padding(
padding: const EdgeInsets.only(top:10),
child:
new Container(
padding: const EdgeInsets.all(0),
child :
new Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
new Container(
height:25,
child :
new Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Icon(Icons.keyboard_arrow_right, color: Colors.white, size: 15.0),
Text('Planning',textAlign:TextAlign.center, style: TextStyle(
color: Colors.black,
fontWeight: FontWeight.w500,
fontSize: SizeConfig.safeBlockHorizontal * 4),
),
Icon(Icons.keyboard_arrow_right, color: Colors.grey, size: 15.0),
]
),
),
Padding(
padding: const EdgeInsets.all(5),),
Icon(Icons.event_available, color:Color(0xffff9a7b), size: 70.0),
],
),
))
),
)),
)
Here is my design
Edit #2
Now I tried to add my assets in the model, but I don't know how to do
void initState() {
// default models list
models = [
Model(index: 0, new Container (
width:90,
height: 90,
child :new FlareActor("assets/pierre.flr", alignment:Alignment.center, fit:BoxFit.contain, animation:"pierre")
), title: 'Coach'),
Model(index: 1, new Image.asset(
"assets/cup.png",
width: 50,
), title: 'Victories'),
Model(index: 2, icon: Icon(Icons.card_giftcard), title: 'Jeux'),
Model(index: 3, icon: Icon(Icons.wb_sunny), title: 'Sunny'),
Model(index: 4, icon: Icon(Icons.cloud), title: 'Cloud'),
Model(index: 5, icon: Icon(Icons.tv), title: 'TV'),
Model(index: 6, icon: Icon(Icons.place), title: 'Location'),
Model(index: 8, icon: Icon(Icons.music_note), title: 'Music'),
// More customization
Model(
index: 7,
icon: Icon(Icons.event_available, color: Color(0xffff9a7b)),
title: 'Planning'),
];
config();
super.initState();
}
This solution isn't a good one and I don't like it, but it works :D
I really appreciate it if anyone would refer a better one
Maybe for this project, it's good to use DB instead of SharedPreferences but here I used SharedPreferences.
The question is how to save the order of some widget(each time on reordering, the order of widget changes and we want to save the order of them, after restarting the app the saved order should be fetched).
SharedPreferences can also save a list of string, so what I did here was:
In the beginning, there should be a default list, that contains the initial order of widget's of the app.
Because widgets are somehow the same and only some of their info is different, I decided to define a model and work with models, instead of a whole complicated widget, I mean when I want to remove or change indexes I do it for a list of models rather than a list of widgets.
Here I supposed the model only contains a title, I also defined an index for it, so all I do is that when I reorder the widget, it reorders the list of models, to save the order, I save the index of models in any order they are now,
for example, if the initial order was [0, 1, 2, 3] let's say after reordering it's now [3, 0, 1, 2], I save this order, and for the next boot, I fetch the saved order([3, 0, 1, 2]) and then reorder the default list based on this fetched order.
Another solution would be to change the model's index and then show an ordered list of models based on their index.
Here is the code:
import 'package:flutter/material.dart';
import 'package:reorderables/reorderables.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() => runApp(
MaterialApp(
home: Scaffold(
body: Center(
child: Page(),
),
),
),
);
class Page extends StatefulWidget {
const Page({Key key}) : super(key: key);
#override
_PageState createState() => _PageState();
}
class _PageState extends State<Page> {
SharedPreferences prefs;
List<Model> models;
#override
void initState() {
// default models list
models = [
Model(index: 0, title: 'Item 0'),
Model(index: 1, title: 'Item 1'),
Model(index: 2, title: 'Item 2'),
Model(index: 3, title: 'Item 3'),
];
config();
super.initState();
}
void config() async {
// Here we reset the default model based on saved order
await SharedPreferences.getInstance().then((pref) {
prefs = pref;
List<String> lst = pref.getStringList('indexList');
List<Model> list = [];
if (lst != null && lst.isNotEmpty) {
list = lst
.map(
(String indx) => models
.where((Model item) => int.parse(indx) == item.index)
.first,
)
.toList();
models = list;
}
setState(() {});
});
}
void _onReorder(int oldIndex, int newIndex) async {
Model row = models.removeAt(oldIndex);
models.insert(newIndex, row);
setState(() {
prefs.setStringList(
'indexList', models.map((m) => m.index.toString()).toList());
});
}
#override
Widget build(BuildContext context) {
return ReorderableWrap(
scrollDirection: Axis.vertical,
direction: Axis.vertical,
spacing: 0.0,
runSpacing: 0,
maxMainAxisCount: 3,
minMainAxisCount: 3,
padding: const EdgeInsets.all(5),
children: models
.map((m) => Card(
child: Container(
child: Text('${m.index} - ${m.title}'),
padding: EdgeInsets.all(24.0),
),
))
.toList(),
onReorder: _onReorder,
onNoReorder: (int index) {
//this callback is optional
debugPrint('${DateTime.now().toString().substring(5, 22)} ' +
'reorder cancelled. index:$index');
},
onReorderStarted: (int index) {
//this callback is optional
debugPrint('${DateTime.now().toString().substring(5, 22)} ' +
'reorder started: index:$index');
});
}
}
class Model {
int index;
String title;
Model({this.index, this.title});
#override
String toString() {
return '$index : $title';
}
}
Changes After Edit to the main question:
This version is based on the editing to the main question, I decided to keep the first answer unchanged because it's a more simple version and may help another viewer.
For the new model, as far as I could get, it has an icon, title, and an onTap functionality, I changed the model to have icon and title, but for the onTap, I wrote my own card version that gets a model and onTap functionality, I could add onTap to the model, but I thought it's better for future use or to use in other places, so I separated the onTap from the model, I also chose Icon for the model, it could be IconData (benefit of IconData is that you can choose customization for each icon and etc).
On my Card version (MyCard), I simply used a GestureDetector and Card to simulate the taps and card.
I wrote a FakePage that gets a model and if you Tap on each card it navigates to this page and shows some message based on the received model.
To clean the previously saved model in SharedPreferences, you should comment the part that fetches models order in config() and on the next refresh, you should uncomment it again.
Here is the new version of code:
import 'package:flutter/material.dart';
import 'package:reorderables/reorderables.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() => runApp(
MaterialApp(
home: Scaffold(
body: Center(
child: Page(),
),
),
),
);
class Page extends StatefulWidget {
const Page({Key key}) : super(key: key);
#override
_PageState createState() => _PageState();
}
class _PageState extends State<Page> {
SharedPreferences prefs;
List<Model> models;
#override
void initState() {
// default models list
models = [
Model(index: 0, icon: Icon(Icons.people), title: 'Coach'),
Model(index: 1, icon: Icon(Icons.wb_incandescent), title: 'Victories'),
Model(index: 2, icon: Icon(Icons.card_giftcard), title: 'Jeux'),
Model(index: 3, icon: Icon(Icons.wb_sunny), title: 'Sunny'),
Model(index: 4, icon: Icon(Icons.cloud), title: 'Cloud'),
Model(index: 5, icon: Icon(Icons.tv), title: 'TV'),
Model(index: 6, icon: Icon(Icons.place), title: 'Location'),
Model(index: 8, icon: Icon(Icons.music_note), title: 'Music'),
// More customization
Model(
index: 7,
icon: Icon(Icons.event_available, color: Color(0xffff9a7b)),
title: 'Planning'),
];
config();
super.initState();
}
void config() async {
// Here we reset the default model based on saved order
await SharedPreferences.getInstance().then((pref) {
prefs = pref;
List<String> lst = pref.getStringList('indexList');
List<Model> list = [];
if (lst != null && lst.isNotEmpty) {
list = lst
.map(
(String indx) => models
.where((Model item) => int.parse(indx) == item.index)
.first,
)
.toList();
models = list;
}
setState(() {});
});
}
void _onReorder(int oldIndex, int newIndex) async {
Model row = models.removeAt(oldIndex);
models.insert(newIndex, row);
setState(() {
prefs.setStringList(
'indexList', models.map((m) => m.index.toString()).toList());
});
}
#override
Widget build(BuildContext context) {
return ReorderableWrap(
spacing: 0.0,
runSpacing: 0,
maxMainAxisCount: 3,
minMainAxisCount: 3,
padding: const EdgeInsets.all(5),
children: <Widget>[
MyCard(
model: models[0],
onTap: () => Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) => FakePage(model: models[0]),
),
),
),
MyCard(
model: models[1],
onTap: () => Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) => FakePage(model: models[1]),
),
),
),
MyCard(
model: models[2],
onTap: () => Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) => FakePage(model: models[2]),
),
),
),
MyCard(
model: models[3],
onTap: () => Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) => FakePage(model: models[3]),
),
),
),
MyCard(
model: models[4],
onTap: () => Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) => FakePage(model: models[4]),
),
),
),
MyCard(
model: models[5],
onTap: () => Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) => FakePage(model: models[5]),
),
),
),
MyCard(
model: models[6],
onTap: () => Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) => FakePage(model: models[6]),
),
),
),
MyCard(
model: models[7],
onTap: () => Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) => FakePage(model: models[7]),
),
),
),
MyCard(
model: models[8],
onTap: () => Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) => FakePage(model: models[8]),
),
),
),
],
onReorder: _onReorder,
onNoReorder: (int index) {
//this callback is optional
debugPrint('${DateTime.now().toString().substring(5, 22)} ' +
'reorder cancelled. index:$index');
},
onReorderStarted: (int index) {
//this callback is optional
debugPrint('${DateTime.now().toString().substring(5, 22)} ' +
'reorder started: index:$index');
});
}
}
// ---------------------- Model --------------------------
class Model {
int index;
String title;
Icon icon;
Model({this.index, this.title, this.icon});
#override
String toString() {
return '$index : $title';
}
}
// ------------------------ MyCard ----------------------------
class MyCard extends StatelessWidget {
final Model model;
final void Function() onTap;
const MyCard({Key key, this.onTap, #required this.model})
: assert(model != null),
super(key: key);
#override
Widget build(BuildContext context) {
double width = MediaQuery.of(context).size.width;
return GestureDetector(
onTap: onTap,
child: Card(
elevation: 8.0,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
child: _child(width),
),
);
}
Widget _child(double width) {
return Container(
width: width / 4,
height: width / 3,
margin: EdgeInsets.all(5.0),
child: Column(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Expanded(
flex: 3,
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text(
model.title,
maxLines: 1,
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.black,
fontWeight: FontWeight.w500,
fontSize: 15.0,
),
overflow: TextOverflow.ellipsis,
),
Icon(
Icons.arrow_forward_ios,
color: Colors.grey.shade400,
size: 15.0,
),
],
),
),
Expanded(
flex: 5,
child: Padding(
padding: EdgeInsets.all(8.0),
child: FittedBox(
fit: BoxFit.contain,
child: model.icon,
),
),
),
],
),
);
}
}
// ----------------------- FAKE PAGE ---------------------------
class FakePage extends StatelessWidget {
final Model model;
const FakePage({Key key, this.model}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.deepOrangeAccent,
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text(
'You Clicked on Card : ${model.title}',
style: TextStyle(fontSize: 20.0),
),
Padding(
padding: EdgeInsets.only(top: 24.0),
child: Icon(
model.icon.icon,
size: 70.0,
),
),
],
),
),
);
}
}
I have an app which generates a new Card wrapped in a GestureDetector when the FAB of Scaffold is pressed. the app was working fine but i wanted to implement a delete card functionality and when i added that, the app doesnt recognize the return statements in the build function. I feel like im missing something obvious but since i am new to flutter i am struggling to find what went wrong.
Whole code:
class _Starting_screenState extends State<Starting_screen> {
int _count = 1;
#override
Widget build(BuildContext context) {
{
List<Widget> cardList = new List.generate(
_count, (int i) => new createCard());
SystemChrome.setEnabledSystemUIOverlays([]);
_deleteNoDo(int id, int index) async {
debugPrint("Deleted Item!");
setState(() {
cardList.removeAt(index);
});
void addItems() async {
setState(() {
cardList.insert(0, new GestureDetector(
onTap: () async {
await Navigator.push(context, MaterialPageRoute(
builder: (context) =>
TodoList(), // this just navigates to another screen ; not important in this question
)
);
},
child: Card(
child: ListTile(
title: Text("project 1"),
trailing: new Listener(
key: new Key(UniqueKey().toString()),
child: new Icon(Icons.remove_circle,
color: Colors.redAccent,),
onPointerDown: (pointerEvent) => _deleteNoDo(id, index),
),
subtitle: whitefontstylemont(text: "project 1",
size: 20,)) //this is just a custom TextStyle
),
));
});
}
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () async {
setState(() {
_count += 1;
});
},
heroTag: "btn2",
child: Icon(Icons.add, color: Color(whitecolor),),
backgroundColor: Color(redcolor),),
body: CustomScrollView(
slivers: <Widget>[
SliverAppBar(
pinned: true,
flexibleSpace: FlexibleSpaceBar(
),
actions: <Widget>[
Expanded(
child: Padding(
padding: const EdgeInsets.only(top: 20, right: 10),
child: whitefontstyle(
text: "Remaining tasks for today - ${cardList
.length}", size: 20,),
),
),
],
),
SliverGrid(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2
),
delegate: new SliverChildBuilderDelegate((context,
index) {
return cardList[index];
},
childCount: cardList.length
)
),
]
)
);
}
}
}
}
delete function:
_deleteNoDo(int id, int index) async {
debugPrint("Deleted Item!");
setState(() {
cardList.removeAt(index);
});
function which adds a card :
void addItems() async {
setState(() {
cardList.insert(0, new GestureDetector(
onTap: () async {
await Navigator.push(context, MaterialPageRoute(
builder: (context) =>
TodoList(), // this just navigates to another screen ; not important in this question
)
);
},
child: Card(
child: ListTile(
title: Text("project 1"),
trailing: new Listener(
key: new Key(UniqueKey().toString()),
child: new Icon(Icons.remove_circle,
color: Colors.redAccent,),
onPointerDown: (pointerEvent) => _deleteNoDo(id, index),
),
subtitle: whitefontstylemont(text: "project 1", size: 20,)) //this is just a custom TextStyle
),
));
});
}
code where cards are displayed in a list
SliverGrid(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2
),
delegate: new SliverChildBuilderDelegate((context, index) {
return cardList[index]; // this is where the cards are displayed in a list
},
childCount: cardList.length
)
)
I have a ListView.builder showing a list, when i click on an item it shows details of that item on the next page (FlashcardDetailsPage).
I'd like to show the next list item when i tap the IconButton in the class FlashcardDetailsPage. So i'd like this button to skip to the next list item. Any ideas?
class FlashcardListView extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: allFlashcards.length,
itemBuilder: (context, int index) {
return ListTile(
title: Text(allFlashcards[index].actReg),
subtitle: Text(allFlashcards[index].question),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => FlashcardDetailPage(
flashcardToShow: allFlashcards[index]),
),
);
},
);
});
}
}
class FlashcardDetailPage extends StatefulWidget {
final Flashcard flashcardToShow;
FlashcardDetailPage({Key key, #required this.flashcardToShow})
: super(key: key);
#override
_FlashcardDetailPageState createState() => _FlashcardDetailPageState();
}
class _FlashcardDetailPageState extends State<FlashcardDetailPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color.fromRGBO(242, 242, 242, 1),
appBar: AppBar(
centerTitle: true,
title: Text(widget.flashcardToShow.actReg),
),
body: Column(
children: <Widget>[
Container(
child: Card(
margin: EdgeInsetsDirectional.fromSTEB(20, 20, 20, 0),
child: Center(
child: Text(
widget.flashcardToShow.question,
textAlign: TextAlign.center,
style: TextStyle(fontSize: 30),
),
)),
),
Container(
height: 100.0,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
IconButton(
icon: Icon(Icons.skip_next),
iconSize: 32.0,
),
],
),
),
],
),
);
}
}
You could just replace the screen with another one showing the next card:
IconButton(
icon: Icon(Icons.skip_next),
iconSiz: 32,
onTap: () {
int currentIndex = allFlashcards.indexOf(widget.flashcardToShow);
if (currentIndex >= allFlashcards.length) return;
var nextFlashcard = allFlashcards[currentIndex + 1];
Navigator.of(context).pushReplacement(MaterialPageRoute(
builder: (ctx) => FlashDetailsPage(flashcardToShow: nextFlashcard)
));
},
)
Thanks Marcel for the direction! I used your logic for a method. To avoid opening a new page every time I pressed the button, i did this & it's working:
void _skipFlashcard () {
setState(() {
int currentIndex = allFlashcards.indexOf(widget.flashcardToShow);
var nextFlashcard = allFlashcards[currentIndex + 1];
widget.flashcardToShow = nextFlashcard;
});
}
IconButton(
icon: Icon(Icons.skip_next),
iconSize: 32.0,
onPressed: _skipFlashcard,