How to Get Custom Stateful Widget value in another class widget? - dart

In My Applicatios's first Page user will select the No from 1 to 10, after selecting the number my second page will display the 5 GradeScreen widget if user select 5, like wise i can create widget using below code, but i can't get the values which user selects from dropdownbutton widget in my GradeScreen widget,
Please help me to get value from this GradeScreen widget.
I'm New to Flutter.
Thanks in Advance.
import 'package:flutter/material.dart';
class CalculateCGPAScreen extends StatelessWidget {
final int noOfSubjectsCount;
CalculateCGPAScreen(this.noOfSubjectsCount);
#override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
appBar: AppBar(),
body: Column(
children: noOfSubjects(noOfSubjectsCount),
),
);
}
List<Widget> noOfSubjects(int subjectCount) {
List<Widget> list = [];
for (int i = 1; i <= subjectCount; i++) {
list.add(GradeScreen("Subject $i", 1));
}
list.add(RaisedButton(
onPressed: () {},
color: Colors.amber,
splashColor: Colors.amberAccent,
child: Text("Calculate CGPA"),
));
return list;
}
}
class GradeScreen extends StatefulWidget {
final String subjectName;
final int gradeSystem;
GradeScreen(this.subjectName, this.gradeSystem);
#override
State<StatefulWidget> createState() {
// TODO: implement createState
return _GradeScreenState();
}
}
class _GradeScreenState extends State<GradeScreen> {
String selectedGrade = "S";
List<int> creditList = [1, 2, 3, 4, 5, 6];
int selectedCredit = 1;
#override
Widget build(BuildContext context) {
// TODO: implement build
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Text(widget.subjectName),
DropdownButton(
items: getGradeList(widget.gradeSystem).map((String loopValue) {
return DropdownMenuItem(
child: Text(loopValue),
value: loopValue,
);
}).toList(),
onChanged: (String value) {
setState(() {
selectedGrade = value;
});
},
value: selectedGrade,
),
DropdownButton(
items: creditList.map((int credit) {
return DropdownMenuItem(
child: Text(credit.toString()),
value: credit,
);
}).toList(),
onChanged: (int value) {
setState(() {
selectedCredit = value;
});
},
value: selectedCredit,
)
],
);
}
List<String> getGradeList(int option) {
List<String> gradeSystem;
if (option == 1) {
gradeSystem = ['S', 'A', 'B', 'C', 'D', 'E'];
} else {
gradeSystem = ['O', 'A+', 'A', 'B+', 'B'];
}
return gradeSystem;
}
}

import 'package:flutter/material.dart';
class CalculateCGPAScreen extends StatelessWidget {
final int noOfSubjectsCount;
CalculateCGPAScreen(this.noOfSubjectsCount);
#override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
appBar: AppBar(),
body: Column(
children: noOfSubjects(noOfSubjectsCount),
),
);
}
List<Widget> noOfSubjects(int subjectCount) {
List<Widget> list = [];
for (int i = 1; i <= subjectCount; i++) {
list.add(GradeScreen("Subject $i", 1));
}
list.add(RaisedButton(
onPressed: () {
for (var item in list) {
if (item is GradeScreen) {
GradeScreen gs = item;
print(gs.grade);
print(gs.credit);
}
}
},
color: Colors.amber,
splashColor: Colors.amberAccent,
child: Text("Calculate CGPA"),
));
return list;
}
}
class GradeScreen extends StatefulWidget {
final String subjectName;
final int gradeSystem;
int _credit;
String _grade;
GradeScreen(this.subjectName, this.gradeSystem);
#override
State<StatefulWidget> createState() {
// TODO: implement createState
return _GradeScreenState();
}
set credit(int value) {
_credit = value;
}
get credit => _credit;
set grade(String value) {
_grade = value;
}
get grade => _grade;
}
class _GradeScreenState extends State<GradeScreen> {
String selectedGrade = "S";
List<int> creditList = [1, 2, 3, 4, 5, 6];
int selectedCredit = 1;
#override
Widget build(BuildContext context) {
// TODO: implement build
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Text(widget.subjectName),
DropdownButton(
items: getGradeList(widget.gradeSystem).map((String loopValue) {
return DropdownMenuItem(
child: Text(loopValue),
value: loopValue,
);
}).toList(),
onChanged: (String value) {
setState(() {
selectedGrade = value;
widget.grade = value;
});
},
value: selectedGrade,
),
DropdownButton(
items: creditList.map((int credit) {
return DropdownMenuItem(
child: Text(credit.toString()),
value: credit,
);
}).toList(),
onChanged: (int value) {
setState(() {
selectedCredit = value;
widget.credit = value;
});
},
value: selectedCredit,
)
],
);
}
List<String> getGradeList(int option) {
List<String> gradeSystem;
if (option == 1) {
gradeSystem = ['S', 'A', 'B', 'C', 'D', 'E'];
} else {
gradeSystem = ['O', 'A+', 'A', 'B+', 'B'];
}
return gradeSystem;
}
}
Change the dropdown value and get the result into debug console.

import 'package:flutter/material.dart';
class CalculateCGPAScreen extends StatelessWidget {
final int noOfSubjectsCount;
CalculateCGPAScreen(this.noOfSubjectsCount);
#override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
appBar: AppBar(),
body: Column(
children: noOfSubjects(noOfSubjectsCount),
),
);
}
List<Widget> noOfSubjects(int subjectCount) {
List<Widget> list = [];
for (int i = 1; i <= subjectCount; i++) {
list.add(GradeScreen("Subject $i", 1));
}
list.add(RaisedButton(
onPressed: () {},
color: Colors.amber,
splashColor: Colors.amberAccent,
child: Text("Calculate CGPA"),
));
return list;
}
}
class GradeScreen extends StatefulWidget {
final String subjectName;
final int gradeSystem;
int _data;
GradeScreen(this.subjectName, this.gradeSystem);
#override
State<StatefulWidget> createState() {
// TODO: implement createState
return _GradeScreenState();
}
set data(int value) {
_data = value;
}
get data => _data;
}
class _GradeScreenState extends State<GradeScreen> {
String selectedGrade = "S";
List<int> creditList = [1, 2, 3, 4, 5, 6];
int selectedCredit = 1;
#override
Widget build(BuildContext context) {
// TODO: implement build
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Text(widget.subjectName),
DropdownButton(
items: getGradeList(widget.gradeSystem).map((String loopValue) {
return DropdownMenuItem(
child: Text(loopValue),
value: loopValue,
);
}).toList(),
onChanged: (String value) {
setState(() {
selectedGrade = value;
});
},
value: selectedGrade,
),
DropdownButton(
items: creditList.map((int credit) {
return DropdownMenuItem(
child: Text(credit.toString()),
value: credit,
);
}).toList(),
onChanged: (int value) {
setState(() {
selectedCredit = value;
widget.data = selectedCredit;
});
},
value: selectedCredit,
)
],
);
}
List<String> getGradeList(int option) {
List<String> gradeSystem;
if (option == 1) {
gradeSystem = ['S', 'A', 'B', 'C', 'D', 'E'];
} else {
gradeSystem = ['O', 'A+', 'A', 'B+', 'B'];
}
return gradeSystem;
}
}

Related

Flutter CustomScrollView and Scoped model problems

I'm facing some issues quite the time lately. I'm using the scoped_model package for my application. But i'm getting the problem that when i'm searching for stuff or i turn my screen to landscape modus it builds twice.
When this happens i'm getting duplicate json because it is rebuilding twice.
I'm getting another issue when i have searched and i go back my custom scroll view is not working anymore. I can't scroll down anymore.
I have writtin the following files:
main.dart:
class GetRelations extends StatefulWidget {
#override
RelationsPage createState() => RelationsPage();
}
class RelationsPage extends State<GetRelations> {
final RelationScopedModel relationScopedModel = RelationScopedModel();
#override
Widget build(BuildContext context) {
relationScopedModel.initializeValues();
return Scaffold(
body: ScopedModel<RelationScopedModel>(
model: relationScopedModel,
child: Container(
child: SearchScreen(),
),
),
);
}
}
search_screen.dart:
class SearchScreen extends StatefulWidget {
#override
SearchScreenState createState() {
return new SearchScreenState();
}
}
class SearchScreenState extends State<SearchScreen> {
final RelationScopedModel relationScopedModel = RelationScopedModel();
ScrollController controller;
int page = 0;
bool atEnd = false;
#override
void initState() {
super.initState();
controller = new ScrollController()..addListener(_scrollListener);
}
#override
void dispose() {
super.dispose();
controller.dispose();
}
void _scrollListener() {
var props = RelationScopedModel.of(context);
/// When reload we check if skip == 0, if skip is 0 then page has to become 0 too.
if (props.skip == 0) {
page = 0;
}
/// Checking if user is at the end of the screen, then let's receive some new content.
if (controller.position.pixels == controller.position.maxScrollExtent) {
/// If it is at the end, we have to set atEnd to true.
if (props.atTheEnd == true) {
atEnd = props.atTheEnd;
return;
}
/// If it has no more pages, return.
if (props.hasMorePages == false) {
return;
}
/// If it is has more stuff, load it in!
if (!props.isLoadingMore && props.hasMorePages) {
page++;
props.getRelations(props.search, page);
}
}
}
#override
Widget build(BuildContext context) {
/// Go back to last page by using WillPopScope.
return WillPopScope(
onWillPop: () {
Navigator.pop(context);
},
child: new Scaffold(
drawer: new DrawerOnly(),
/// We are using Scoped Model to load the json in the sliver list.
body: ScopedModelDescendant<RelationScopedModel>(
builder: (context, child, model) {
return CustomScrollView(
controller: controller,
slivers: <Widget>[
SliverAppBar(
title: SearchWidget(
performSearch: model.getRelations,
),
floating: true,
pinned: true,
),
model.isLoading && model.atTheEnd
? SliverFillRemaining(
child: Center(
child: CircularProgressIndicator(),
),
)
: model.getRelationCount() < 1
? SliverFillRemaining(
child: Center(
child: Text(
model.statusText,
style: Theme.of(context).textTheme.headline,
),
),
)
: SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
if (index == model.getRelationCount() + 1) {
if (model.hasMorePages == true) {
return Padding(
padding: const EdgeInsets.symmetric(
vertical: 16.0),
child: Center(
child: CircularProgressIndicator()),
);
}
return Container(width: 0, height: 0);
} else if (index == 0) {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: Colors.grey[300]))),
child: Text(
"Relaties",
style: Theme.of(context)
.textTheme
.body2
.copyWith(color: Colors.white),
),
);
} else {
return Container(
child: Column(
children: <Widget>[
InkWell(
child: RelationItem(
model.relation[index - 1]),
onTap: () {
var id =
model.relation[index - 1].id;
Navigator.of(context).push(
new MaterialPageRoute(
builder: (BuildContext
context) =>
new DetailScreen(id)));
},
),
],
),
);
}
},
childCount: model.getRelationCount() + 2,
),
)
],
);
},
),
),
);
}
}
search.dart (this is my searchwidget):
class SearchWidget extends StatelessWidget { final performSearch;
const SearchWidget({Key key, #required this.performSearch}) : super(key: key);
#override Widget build(BuildContext context) {
print('wat gebeurt er');
return Card(
elevation: 3.0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(2.0)),
),
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 15.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
GestureDetector(
child: Icon(
Icons.search,
color: Colors.white,
),
onTap: () {},
),
SizedBox(
width: 10.0,
),
Expanded(
child: TextField(
decoration: InputDecoration(
hintStyle: TextStyle(fontSize: 14.0, color: Colors.white),
border: InputBorder.none,
hintText: "Zoeken..."),
onChanged: (String search) {
if (search.length > 2) {
performSearch(search);
}
if (search.length == 0) {
performSearch(search);
}
},
),
),
InkWell(
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(builder: (context) => SettingsScreen()));
},
child: Icon(
FontAwesomeIcons.slidersH,
color: Colors.white,
),
),
],
),
),
); } }
relation_scoped_model.dart:
class RelationScopedModel extends Model {
List<GetRelation> _relation = [];
List<GetContacts> _contacts = [];
List<GetLocations> _locations = [];
bool _isLoading = false;
String _statusText = "Start met zoeken...";
bool _hasMorePages = true;
int _skip = 0;
int skipMore = 0;
String _search;
bool _isLoadingMore = false;
bool atTheEnd = false;
List<Map<String, String>> _listingTypeList = [
{"name": "Buy", "value": "buy"},
{"name": "Rent", "value": "rent"},
];
String _listingType;
List<Map<String, String>> _sortList = [
{"name": "Relevancy", "value": "relevancy"},
{"name": "Bedroom (Ascending)", "value": "bedroom_lowhigh"},
{"name": "Bedroom (Descending)", "value": "bedroom_highlow"},
{"name": "Price (Ascending)", "value": "price_lowhigh"},
{"name": "Price (Descending)", "value": "price_highlow"},
{"name": "Newest", "value": "newest"},
{"name": "Oldest", "value": "oldest"},
{"name": "Random", "value": "random"},
{"name": "Distance", "value": "distance"}
];
String _sort;
List<GetRelation> get relation => _relation;
List<GetContacts> get contacts => _contacts;
List<GetLocations> get locations => _locations;
bool get isLoading => _isLoading;
String get statusText => _statusText;
bool get hasMorePages => _hasMorePages;
String get search => _search;
int get skip => _skip;
bool get isLoadingMore => _isLoadingMore;
List<Map<String, String>> get listingTypeList => _listingTypeList;
String get listingType => _listingType;
List<Map<String, String>> get sortList => _sortList;
String get sort => _sort;
int getRelationCount() => _relation.length;
void initializeValues() async {
_relation.clear();
SharedPreferences prefs = await SharedPreferences.getInstance();
_listingType = prefs.getString('listingType') ?? 'rent';
_sort = prefs.getString('sort') ?? 'relevancy';
getRelations(search);
}
var _skiptotal = 10;
var lastSearch;
Future<dynamic> _getData(String search, [int page = 0]) async {
/// When page is 0 we don't have to skip content, if page is 1 or higher it will have to skip content
if (page != 0) {
var skipPage = page * _skiptotal;
_skip = skipPage;
}
/// If page == 0, we have to set page and _skip to 0.
else {
_skip = 0;
page = 0;
}
/// When nothing is filled in the input search, we have to set search to single quotes because otherwise it will search on null.
if (search == null) {
search = '';
}
if (lastSearch != search) {
_relation.clear();
lastSearch = '';
lastSearch = search;
page + 1;
}
String _credentials;
SharedPreferences pref = await SharedPreferences.getInstance();
_credentials = (pref.getString("credentials") ?? "Empty");
var res = await http.get(
Uri.encodeFull("$cf_api_RelationsUrl" +
"?$cf_api_SkipParameter=$_skip&$cf_api_SearchParameter=$search&$cf_api_LimitParameter=10"),
headers: {
"content-type": "application/json",
"accept": "application/json",
'cookie': '$_credentials'
});
var decodedJson = json.decode(res.body, reviver: (k, v) {
if (k == "status" && v == false) {
_hasMorePages = false;
return v;
} else {
return v;
}
});
if (_hasMorePages == false) {
return decodedJson;
} else {
List list = List();
list = json.decode(res.body) as List;
if (list.length < 10) {
atTheEnd = true;
_hasMorePages = false;
return decodedJson;
}
}
return decodedJson;
}
Future getRelations(String search, [int page = 0]) async {
if (page == 0) {
page = 0;
_isLoading = true;
_relation.clear();
} else {
_isLoadingMore = true;
}
_search = search;
var responseData = await _getData(search, page);
notifyListeners();
var result = responseData
.map(
(data) => serializers.deserializeWith(GetRelation.serializer, data))
.toList();
result.forEach((relations) {
_relation.add(relations);
});
if (result.isEmpty) {
_statusText = "Nothing Found";
}
if (page == 0) {
_isLoading = false;
} else {
_isLoadingMore = false;
}
if (atTheEnd == true) {
return true;
}
// notifyListeners();
}
void setListingType(String value) async {
_listingType = value;
getRelations(search);
notifyListeners();
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setString('listingType', _listingType);
}
void setSort(String value) async {
_sort = value;
getRelations(search);
notifyListeners();
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setString('sort', _sort);
}
/// Wraps [ScopedModel.of] for this [Model].
static RelationScopedModel of(BuildContext context) =>
ScopedModel.of<RelationScopedModel>(context);
}
I'm guessing that the problem is that my scopedmodeldescendant isn't in the init state. But i'm not sure and i hope somebody knows what the problem is with this code.
Thanks in advance
Greetings,
Jente
UPDATE:
I got my customscrollview is working when i set atTheEnd to false and hasMorePages to true in my relation_scoped_model at initializeValues().
The only problem i'm still facing is that i'm getting duplicates, which happens because my screen is rebuilding twice.
When using scoped model, you should wrap the widgets that should respond to changes one by one with scoped model descendants. This way only those widgets can rebuild rather than the whole page rebuilding.

How to remove or update selected object in list?

I made a simple flutter app, I have a screen and a listView, and data in the list can be deleted or updated.
But when i try to update or remove one of them, all my data in list being updated/deleted, what did i missed?
I do the following:
ListTile(
title: Text(child.name.toString()),
subtitle: Text('${child.amount}'),
onTap: () => _onChildTap(child),
trailing: GestureDetector(
onTap: () { setState(() { children.remove(child); }); },
child: Icon(Icons.delete)
),
)
void _onChildTap(ChildModel child) {
double amount = 5;
setState(() { child.amount += amount; });
}
For update and delete the data.
Below is my full code:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(primarySwatch: Colors.blue),
home: MyScreen()
);
}
}
class ParentModel {
ParentModel({this.id, this.name, this.children});
int id;
String name;
List<ChildModel> children;
}
class ChildModel {
ChildModel({this.id, this.name, this.amount});
int id;
String name;
double amount;
}
class MyScreen extends StatefulWidget {
#override
_MyScreenState createState() => _MyScreenState();
}
class _MyScreenState extends State<MyScreen> {
List<ParentModel> _list = [];
void _onAdd() {
ChildModel child = ChildModel(id: 1, name: 'Child 1', amount: 1);
List<ChildModel> children = [];
children.add(child);
List<ParentModel> results = [];
results.add(ParentModel(id: 1, name: 'Parent 1', children: children));
results.add(ParentModel(id: 2, name: 'Parent 2', children: children));
results.add(ParentModel(id: 3, name: 'Parent 3', children: children));
if (_list.isEmpty) {
setState(() { _list.addAll(results); });
}
else{
results.forEach((parent) {
if (!_isExistInParentList(parent.id)) {
setState(() { _list.add(parent); });
}
else {
parent.children.forEach((child) {
if (!_isExistInChildList(parent.id, child.id)) {
setState(() {
_list.firstWhere((parent) => parent.id == parent.id).children.add(child);
});
}
});
}
});
}
}
bool _isExistInParentList(int parentId) {
for (ParentModel parent in _list) {
if (parent.id == parentId) { return true; }
}
return false;
}
bool _isExistInChildList(int parentId, int childId) {
for (ParentModel parent in _list) {
if (parent.id == parentId) {
for (ChildModel child in parent.children) {
if (child.id == childId) {
return true;
}
}
}
}
return false;
}
void _onChildTap(ChildModel child) {
double amount = 5;
setState(() { child.amount += amount; });
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Flutter List'),
actions: <Widget>[
IconButton(
icon: Icon(Icons.add),
onPressed: _onAdd
)
],
),
body: Column(
children: <Widget>[
Expanded(
child: ListView.builder(
itemCount: _list.length,
itemBuilder: (context, index) {
ParentModel parent = _list[index];
return Column(
children: <Widget>[
ExpansionTile(
title: Text(parent.name),
initiallyExpanded: true,
children: _buildChildren(parent.children)
),
SizedBox(height: 10)
],
);
}
),
),
],
),
);
}
List<Widget> _buildChildren(List<ChildModel> children) {
List<Widget> widgets = [];
children.forEach((child) {
widgets.add(
ListTile(
title: Text(child.name.toString()),
subtitle: Text('${child.amount}'),
onTap: () => _onChildTap(child),
trailing: GestureDetector(
onTap: () { setState(() { children.remove(child); }); },
child: Icon(Icons.delete)
),
)
);
});
return widgets;
}
}
I would really appreciate your help.
I believe your problem is here:
List<ParentModel> results = [];
results.add(ParentModel(id: 1, name: 'Parent 1', children: children));
results.add(ParentModel(id: 2, name: 'Parent 2', children: children));
results.add(ParentModel(id: 3, name: 'Parent 3', children: children));
You are creating multiple instances of ParentModel, but assigning same instance of children, so basically children are shared through 3 different parents, if you update children of one of these parents, all 3 parents will be updated in same way.

DragTarget onWillAccept and onAccept not firing

I'm starting with Flutter and I cannot make drag and drop functionality to work. I followed the documentation but have no idea what I'm doing wrong.
This sample app displays three squares and the blue is draggable. The other ones have DragTarget set, one inside the square and one outside the square. When I drag the blue square it prints info that the drag started but there is no print info when dragging or dropping over the DragTargets.
Here is the code:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.red,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Container(
constraints: BoxConstraints.expand(),
color: Colors.grey[900],
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
width: 100,
height: 100,
color: Colors.red,
child: DragTarget(
onWillAccept: (d) => true,
onAccept: (d) => print("ACCEPT 1!"),
onLeave: (d) => print("LEAVE 1!"),
builder: (a,data,c) {
print(data);
return Center();
},
),
),
DragTarget(
onWillAccept: (d){return true;},
onAccept:(d) => print("ACCEPT 2!"),
onLeave: (d) => print("LEAVE 2!"),
builder: (context, candidateData, rejectedData){
return Container(
width: 150,
height: 150,
color: Colors.purple
);
}
),
Draggable(
data: ["SOME DATA"],
onDragStarted: () => print("DRAG START!"),
onDragCompleted: () => print("DRAG COMPLETED!"),
onDragEnd: (details) => print("DRAG ENDED!"),
onDraggableCanceled: (data, data2) => print("DRAG CANCELLED!"),
feedback: SizedBox(
width: 100,
height: 100,
child: Container(
margin: EdgeInsets.all(10),
color: Colors.green[800],
),
),
child: SizedBox(
width: 100,
height: 100,
child: Container(
margin: EdgeInsets.all(10),
color: Colors.blue[800],
),
),
),
],
)
),
)
);
}
}
Apparently the Draggable and DragTarget need to have the generic type specified if you are passing data, otherwise the onAccept and onWillAccept will not be fired.
For example, if you want to pass data as int then use Draggable<int> and DragTarget<int> — this also applies to onAccept and onWillAccept, they need to accept int as a parameter.
You should setState when you call onAccept and add a boolean value to your stateful widget.
bool accepted = false;
onAccept: (data){
if(data=='d'){
setState(() {
accepted = true;
});
},
I used ChangeNotifyProvider and a model to manage my Draggable and Dragable Target multiplication challenge and results. I built a simple multiplication game using ChangeNotify that updates the Provider that is listening for changes. The GameScore extends the ChangeNotifier which broadcast to the provider when changes occur in the model. The Provider can either be listening or not listening. If the user get the right answer than the Model updates its score and notifies the listeners. The score is then displayed in a text box. I think the provider model is a simplier way to interact with the widget for managing state.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'dart:math';
class Multiplication
{
int value1;
int value2;
int result;
int answerKey;
int fakeResult;
Multiplication(this.value1,this.value2,this.result,this.answerKey,this.fakeResult);
}
class GameScore with ChangeNotifier
{
int score=0;
int number=0;
List<Multiplication> lstMultiplication=[];
late Multiplication currentMultiplication;
GameScore()
{
var rng = Random();
for(int i=0; i<=25; i++)
{
for(int j=0; j<=25; j++)
{
var answerKey=rng.nextInt(2);
var fakeAnswer=rng.nextInt(25)*rng.nextInt(25);
lstMultiplication.add(Multiplication(i,j,i*j,answerKey,fakeAnswer));
}
}
}
int getChallengeValue(int key)
{
int retVal=0;
if (currentMultiplication.answerKey==key)
{
retVal=currentMultiplication.result;
}
else
{
retVal=currentMultiplication.fakeResult;
}
return retVal;
}
String displayMultiplication()
{
String retVal="";
if (currentMultiplication!=null)
{
retVal=currentMultiplication.value1.toString()+ " X "+currentMultiplication.value2.toString();
}
return retVal;
}
nextMultiplication()
{
var rng = Random();
var index=rng.nextInt(lstMultiplication.length);
currentMultiplication= lstMultiplication[index];
}
changeAcceptedData(int data) {
var rng = Random();
score += 1;
number=rng.nextInt(100);
notifyListeners();
}
changeWrongData(int data) {
var rng = Random();
score -= 1;
number=rng.nextInt(100);
notifyListeners();
}
}
void main() {
runApp(const MyApp());
//runApp(Provider<GameScore>(create: (context) => GameScore(), child: MyApp()));
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: //TestDraggableWidget(),
ChangeNotifierProvider(create:(context)=>GameScore(),child: TestDraggableWidget())
);
}
}
class TestDraggableWidget extends StatefulWidget {
TestDraggableWidget({Key? key}) : super(key: key);
#override
State<TestDraggableWidget> createState() => _TestDraggableWidgetState();
}
class _TestDraggableWidgetState extends State<TestDraggableWidget> {
#override
Widget build(BuildContext context) {
Provider.of<GameScore>(context,listen:false).nextMultiplication();
return Scaffold(appBar: AppBar(title:Text("Draggable")),body:
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
EvenContainerWidget(),
NumberContainerWidget(),
OddContainerWidget(),
SizedBox(width:100,child:Text("Score: ${Provider.of<GameScore>(context, listen: true).score}",style:TextStyle(color:Colors.green,fontSize:14)))
],)
],));
}
}
class EvenContainerWidget extends StatefulWidget {
EvenContainerWidget({Key? key}) : super(key: key);
#override
State<EvenContainerWidget> createState() => _EvenContainerWidgetState();
}
class _EvenContainerWidgetState extends State<EvenContainerWidget> {
int? valueAccepted;
_onAccept(BuildContext context, int data)
{
if (data==valueAccepted){
Provider.of<GameScore>(context, listen: false).changeAcceptedData(data);
setState(() {
valueAccepted=data;
});
}
else
{
Provider.of<GameScore>(context, listen: false).changeWrongData(data);
}
}
bool _willAccept(int? data)
{
return true;
}
#override
Widget build(BuildContext context) {
valueAccepted=Provider.of<GameScore>(context, listen: false).getChallengeValue(1);
return Container(
width:60,
height:60,
decoration:BoxDecoration(borderRadius: BorderRadius.circular(10),color:Colors.blueAccent),
child:
DragTarget<int>(
onAccept: (data)=> _onAccept(context,data),
onWillAccept: _willAccept,
builder:(context, candidateData, rejectedData) {
return Center(child:Text("Choice 1: ${valueAccepted==null?'':valueAccepted.toString()}"));
},
)
);
}
}
class OddContainerWidget extends StatefulWidget {
OddContainerWidget({Key? key}) : super(key: key);
#override
State<OddContainerWidget> createState() => _OddContainerWidgetState();
}
class _OddContainerWidgetState extends State<OddContainerWidget> {
int? valueAccepted;
_onAccept(BuildContext context, int data)
{
if(data==valueAccepted)
{
Provider.of<GameScore>(context, listen: false).changeAcceptedData(data);
setState(() {
valueAccepted=data;
});
}
else
{
Provider.of<GameScore>(context, listen: false).changeWrongData(data);
}
}
bool _willAccept(int? data)
{
/*if (data!.isOdd)
{
setState(() {
valueAccepted=data;
});
}*/
return true;
}
#override
Widget build(BuildContext context) {
valueAccepted=Provider.of<GameScore>(context, listen: false).getChallengeValue(0);
return Container(
width:60,
height:60,
decoration:BoxDecoration(borderRadius: BorderRadius.circular(10),color:Colors.blueAccent),
child:
DragTarget<int>(
onAccept: (data)=> _onAccept(context,data),
onWillAccept: _willAccept,
builder:(context, candidateData, rejectedData) {
return Center(child:Text("Choice 2: ${valueAccepted==null?'':valueAccepted.toString()}"));
},
)
);
}
}
class NumberContainerWidget extends StatelessWidget {
const NumberContainerWidget({Key? key}) : super(key: key);
_dragCompleted(BuildContext context){
}
#override
Widget build(BuildContext context) {
return Draggable(
//information dropped by draggable at dragtarget
data: Provider.of<GameScore>(context, listen: true).currentMultiplication.result,
onDragCompleted: _dragCompleted(context) ,
//Widget to be displayed when drag is underway
feedback: Container(
width:60,
height:60,
decoration:BoxDecoration(borderRadius: BorderRadius.circular(10),color:Colors.black26),
child: Center(child:Text("${Provider.of<GameScore>(context, listen: false).displayMultiplication()}",style:TextStyle(color:Colors.green,fontSize:14))),
),
child:
Container(
width:60,
height:60,
decoration:BoxDecoration(borderRadius: BorderRadius.circular(10),color:Colors.black26),
child: Center(child:Text("${Provider.of<GameScore>(context, listen: false).displayMultiplication()}",style:TextStyle(color:Colors.blue,fontSize:14))),
));
}
}

How can I have an AppBar icon trigger a rendering?

I think I have the design of my app wrong. I am new to flutter/dart and am finding myself confused by previous experience with other languages (specifically C# and JavaScript).
I have an app that currently consists of a 3 x 3 GridView of 9 colored circular tiles, named Tiled_Surface. Each tile is assigned the following onTap handler:
void on_tile_tapped(int index) {
setState(() {
tile_tapped = index;
});
} // on_tile_tapped
where index has an arbitrary value in the range [0..8). Whenever a tile is tapped, the color of the tile changes to a lighter value (actually the "accent color" of the tile's color). All of that works file.
The AppBar contains a title ("Tiled Surface Demo") and two actions that consist of two IconButtons (Icons.swap_horiz and Icons.replay). It is intended that when the swap icon is tapped that the tile colors are shuffled into a new random order. And when the replay icon is tapped the tile colors are restored to their original order. When the two AppBar icons are tapped there is no apparent change to the display until a tile is tapped. Then, the changes made by the AppBar taps are displayed.
This is not the desired effect. My problem is how to render Tiled_Surface when the AppBar icons are tapped.
The code for the app follows. Thanks for your thoughts.
// ignore_for_file: camel_case_types
// ignore_for_file: constant_identifier_names
// ignore_for_file: non_constant_identifier_names
import 'package:flutter/material.dart';
import 'dart:math';
const int NUMBER_TILES = 9;
final int cross_axis_count = (sqrt (NUMBER_TILES)).toInt();
final double cross_axis_spacing = 4.0;
final double main_axis_spacing = cross_axis_spacing;
List<int> indices = [ 0, 1, 2, 3, 4, 5, 6, 7, 8 ];
List normal_colors = [
Colors.red,
Colors.orange,
Colors.yellow,
Colors.green,
Colors.blue,
Colors.purple,
Colors.amber,
Colors.cyan,
Colors.indigo,
]; // normal_colors
List bright_colors = [
Colors.pinkAccent,
Colors.orangeAccent,
Colors.yellowAccent,
Colors.lightGreenAccent,
Colors.blue.shade200,
Colors.purpleAccent,
Colors.amberAccent,
Colors.cyanAccent,
Colors.indigoAccent,
]; // bright_colors
void reinitialize_tiles() {
indices.clear();
for (int i = 0; (i < NUMBER_TILES); i++) {
indices.add(i);
}
} // reinitialize_tiles
void randomize_tiles() {
var random = new Random();
indices.clear();
for (int i = 0; (i < NUMBER_TILES); i++) {
var varient = random.nextInt(9);
if (indices.length > 0) {
while (indices.contains(varient)) {
varient = random.nextInt(9);
}
}
indices.add(varient);
}
} // randomize_tiles
void main() => runApp(new MyApp());
class Tiled_Surface extends StatefulWidget {
Tiled_Surface({Key key}) : super(key: key);
#override // Tiled_Surface
Tiled_Surface_State createState() => Tiled_Surface_State();
}
class Tiled_Surface_State extends State<Tiled_Surface> {
List<GridTile> grid_tiles = <GridTile>[];
int tile_tapped = -1;
void on_tile_tapped(int index) {
setState(() {
tile_tapped = index;
});
} // on_tile_tapped
GridTile new_surface_tile(Color tile_color, int index) {
GridTile tile = GridTile(
child: GestureDetector(
onTap: () => on_tile_tapped(index),
child: Container(
decoration: BoxDecoration(
color: tile_color,
shape: BoxShape.circle,
),
),
)
);
return (tile);
} // new_surface_tile
List<GridTile> create_surface_tiles() {
grid_tiles = new List<GridTile>();
for (int i = 0; (i < NUMBER_TILES); i++) {
Color tile_color = ( tile_tapped == i ) ?
bright_colors[indices[i]] :
normal_colors[indices[i]];
grid_tiles.add(new_surface_tile(tile_color, i));
}
return (grid_tiles);
} // create_surface_tiles
#override // Tiled_Surface_State
Widget build(BuildContext context) {
return GridView.count(
shrinkWrap: true,
crossAxisCount: cross_axis_count,
childAspectRatio: 1.0,
padding: const EdgeInsets.all(4.0),
mainAxisSpacing: main_axis_spacing,
crossAxisSpacing: cross_axis_spacing,
children: create_surface_tiles(),
);
}
} // class Tiled_Surface_State
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Tiled Surface Demo',
home: Scaffold(
appBar: AppBar(
title: Text('Tiled Surface Demo'),
actions: <Widget>[
IconButton(
icon: Icon(Icons.swap_horiz),
onPressed: () {
randomize_tiles();
},
),
IconButton(
icon: Icon(Icons.replay),
onPressed: () {
reinitialize_tiles();
},
)
]
),
body: Column(
children: [
Tiled_Surface(),
],
),
),
);
}
}
Problem:
Flutter widgets(Stateful) will react to state variables only. Not for global and local. In your example indices is a global variable.
I updated the code with
Moved indices into MyApp as
Mutable global variables are not good
We want our MyApp to reflect for changes in indices
As MyApp started holding state changed it as StatefulWidget
Moved randomize_tiles and reinitialize_tiles into _MyAppState and added setState on change of indices so that widgets will get re-rendered.
As Tiled_Surface also need indices, injecting(passing) them in the constructor.
Please have a look
import 'package:flutter/material.dart';
import 'dart:math';
const int NUMBER_TILES = 9;
final int cross_axis_count = (sqrt(NUMBER_TILES)).toInt();
final double cross_axis_spacing = 4.0;
final double main_axis_spacing = cross_axis_spacing;
List normal_colors = [
Colors.red,
Colors.orange,
Colors.yellow,
Colors.green,
Colors.blue,
Colors.purple,
Colors.amber,
Colors.cyan,
Colors.indigo,
]; // normal_colors
List bright_colors = [
Colors.pinkAccent,
Colors.orangeAccent,
Colors.yellowAccent,
Colors.lightGreenAccent,
Colors.blue.shade200,
Colors.purpleAccent,
Colors.amberAccent,
Colors.cyanAccent,
Colors.indigoAccent,
]; // bright_colors
void main() => runApp(new MyApp());
class Tiled_Surface extends StatefulWidget {
List<int> indices;
Tiled_Surface(this.indices, {Key key}) : super(key: key);
#override // Tiled_Surface
Tiled_Surface_State createState() => Tiled_Surface_State(indices);
}
class Tiled_Surface_State extends State<Tiled_Surface> {
List<GridTile> grid_tiles = <GridTile>[];
int tile_tapped = -1;
List<int> indices;
Tiled_Surface_State(this.indices);
void on_tile_tapped(int index) {
setState(() {
tile_tapped = index;
});
} // on_tile_tapped
GridTile new_surface_tile(Color tile_color, int index) {
GridTile tile = GridTile(
child: GestureDetector(
onTap: () => on_tile_tapped(index),
child: Container(
decoration: BoxDecoration(
color: tile_color,
shape: BoxShape.circle,
),
),
));
return (tile);
} // new_surface_tile
List<GridTile> create_surface_tiles() {
grid_tiles = new List<GridTile>();
for (int i = 0; (i < NUMBER_TILES); i++) {
Color tile_color = (tile_tapped == i)
? bright_colors[indices[i]]
: normal_colors[indices[i]];
grid_tiles.add(new_surface_tile(tile_color, i));
}
return (grid_tiles);
} // create_surface_tiles
#override // Tiled_Surface_State
Widget build(BuildContext context) {
return GridView.count(
shrinkWrap: true,
crossAxisCount: cross_axis_count,
childAspectRatio: 1.0,
padding: const EdgeInsets.all(4.0),
mainAxisSpacing: main_axis_spacing,
crossAxisSpacing: cross_axis_spacing,
children: create_surface_tiles(),
);
}
} // class Tiled_Surface_State
class MyApp extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return new _MyAppState();
}
}
class _MyAppState extends State<MyApp> {
List<int> indices = [0, 1, 2, 3, 4, 5, 6, 7, 8];
void randomize_tiles() {
var random = new Random();
indices.clear();
for (int i = 0; (i < NUMBER_TILES); i++) {
var varient = random.nextInt(9);
if (indices.length > 0) {
while (indices.contains(varient)) {
varient = random.nextInt(9);
}
}
indices.add(varient);
}
setState(() {});
}
void reinitialize_tiles() {
indices.clear();
for (int i = 0; (i < NUMBER_TILES); i++) {
indices.add(i);
}
setState(() {});
}
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Tiled Surface Demo',
home: Scaffold(
appBar: AppBar(title: Text('Tiled Surface Demo'), actions: <Widget>[
IconButton(
icon: Icon(Icons.swap_horiz),
onPressed: () {
randomize_tiles();
},
),
IconButton(
icon: Icon(Icons.replay),
onPressed: () {
reinitialize_tiles();
},
)
]),
body: Column(
children: [
Tiled_Surface(indices),
],
),
),
);
}
}

Flutter - SwitchListTile - DRY code

Complete newbie, so bear with me. Just a question on how I could refactor my code using the DRY principle. I'm sure it can be done on such a simple example so here goes.... My code below shows three 'switchListTiles'. I've managed to work out how to create 3 switchListTiles on top of one another, and also how to get them to turn on/off individually. Its just that this means I'm creating the following function 3 times:
bool _value3 = false;
void _onChanged3(bool value3) {
setState(() {
_value3 = value3;
});
}
I'm sure there is a way I could change this so I don't have the same code three times.
Any help would be greatly appreciated
Many thanks in advance
import 'package:flutter/material.dart';
void main() {
runApp(new MaterialApp(
title: "Switch Widget Demo",
home: new MyApp(),
));
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
bool _value = false;
void _onChanged(bool value) {
setState(() {
_value = value;
});
}
bool _value2 = false;
void _onChanged2(bool value2) {
setState(() {
_value2 = value2;
});
}
bool _value3 = false;
void _onChanged3(bool value3) {
setState(() {
_value3 = value3;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Switch Demo"),
backgroundColor: Colors.redAccent,
centerTitle: true,
),
body: Container(
padding: EdgeInsets.all(32.0),
child: Column(
children: <Widget>[
Switch(value: _value,
onChanged: (bool value) {_onChanged(value);}),
SwitchListTile(value: _value,
title: Text("Click Me"),
activeColor: Colors.red,
secondary: Icon(Icons.home),
subtitle: Text("For my small print"),
onChanged: (bool value) {_onChanged(value);}),
SwitchListTile(value: _value2,
title: Text("Click Me Again Please"),
activeColor: Colors.lightGreen,
secondary: Icon(Icons.perm_identity),
onChanged: (bool value2) {_onChanged2(value2);}),
SwitchListTile(value: _value3,
title: Text("And Again Please"),
activeColor: Colors.blueGrey,
subtitle: Text("Some more small print"),
secondary: Icon(Icons.person),
onChanged: (bool value) {_onChanged3(value);}),
],
),
),
);
}
}
You just need to refactor the SwitchListTiles into its separate class, then make List<SwitchListTiles> in the parent widget.
Here I create 20 of them with lesser code:
class MySwitchListTilesContainer extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey[800],
body: ListView(
children: List.generate(20, (i)=>MySwitchListTile(
)),
),
);
}
}
class MySwitchListTile extends StatefulWidget {
#override
_MySwitchListTileState createState() => new _MySwitchListTileState();
}
class _MySwitchListTileState extends State<MySwitchListTile> {
bool _v = false;
#override
Widget build(BuildContext context) {
return SwitchListTile(
value:_v,
onChanged: (value)=>setState((){
_v=value;
}),
);
}
}

Resources