Implement setstat and bind variables - flutter

I'm trying to get and display 2 variable values from another dart file, ("int myId" and "String myMenu") , these variables are updated with every "onTap" widget, my code works, but only if i do a "hot reload", i think that i need to put a "setstate" somewhere, but i'm having difficulty to implement it.
I think the problem is there, my widget text returns "null" to me, but if I hit the menu button and do a "hot reload", it's ok.
displayText.dart
import 'package:flutter/material.dart';
import './menu.dart';
class display extends StatefulWidget {
int myId;
String myMenu;
display(this.myId, this.myMenu);
#override
_displayState createState() => _displayState();
}
class _displayState extends State<display> {
Future myVarUsed() async {
//Each press on the button return the value
setState(() {
print('myIdDsiplay: ${widget.myId}'); // null
print('myMenuDisplay : ${widget.myMenu}'); // null
});
}
#override
void initState() {
super.initState();
myVarUsed();
}
#override
Widget build(BuildContext context) {
return Container(
color: Colors.blue,
height: 250,
width: 250,
child: Row(
children: [
Text('My ID is : ${widget.myId}'),
Text('My menu is : ${widget.myMenu}'),
],
),
);
}
}
This file contains the menu inside a scrollbar, each button return the ID and the name (of the button) and store it in 2 variable ("int myId" and "String myMenu") that i want to pass.
menu.dart
import 'package:flutter/material.dart';
import './mylist.dart';
import './displayText.dart';
class Menu extends StatefulWidget {
static int myId;
static String myMenu;
#override
_MenuState createState() => _MenuState();
}
class _MenuState extends State<Menu> {
Container scrollList() {
final PageController controller = PageController(initialPage: 1, keepPage: true, viewportFraction: 0.35);
return Container(
color: Colors.red,
height: 90,
child: PageView.builder(
scrollDirection: Axis.horizontal,
controller: controller,
itemCount: listdata.length,
physics: BouncingScrollPhysics(),
itemBuilder: (BuildContext context, int index) {
return Container(
child: gestureDetector_Ontap(index),
);
},
),
);
}
GestureDetector gestureDetector_Ontap(int index) {
return GestureDetector(
onTap: () {
Menu.myId = listdata[index].id;
Menu.myMenu = listdata[index].menuObj;
display(Menu.myId, Menu.myMenu);
print('myIDMenu ${Menu.myId}');
print('myMenuMenu ${Menu.myMenu}');
},
child: Container(
alignment: AlignmentDirectional.center,
child: Text(
'${listdata[index].menuObj}',
),
),
);
}
Widget build(BuildContext context) {
return Container(
child: scrollList(),
);
}
}
This file contains my list and his class
mylist.dart
class listModel {
int id;
String menuObj;
listModel(this.id, this.menuObj);
}
List listdata = [
listModel(0, 'Menu01'),
listModel(1, 'Menu02'),
listModel(2, 'Menu03'),
listModel(3, 'Menu04'),
listModel(4, 'Menu05')
];
And the container
main.dart
import 'package:flutter/material.dart';
import './menu.dart';
import './displayText.dart';
import './mylist.dart';
void main() {
runApp(MyHomePage());
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Container(
child: Column(
children: <Widget>[
Menu(),
display(Menu.myId, Menu.myMenu),
],
),
),
),
);
}
}

The problem
You're defining Menu this way:
class Menu extends StatefulWidget {
static int myId;
static String myMenu;
#override
_MenuState createState() => _MenuState();
}
When your app starts, myId and myMenu are uninitialized variables, therefore they're implicitely set to null.
Inside _MyHomePageState, you call
display(Menu.myId, Menu.myMenu)
Since you haven't initialized Menu.myId and Menu.myMenu yet, they're still null.
When you tap the GestureDetector, you initialize Menu.myId and Menu.myMenu this way:
Menu.myId = listdata[index].id;
Menu.myMenu = listdata[index].menuObj;
display(Menu.myId, Menu.myMenu);
print('myIDMenu ${Menu.myId}');
print('myMenuMenu ${Menu.myMenu}');
Now, Menu.myId and Menu.myMenu are defined to non-null values. However, this will not update the Container's display(Menu.myId, Menu.myMenu), so they'll still be null, you need to update it by yourself.
The solution
I've added comments through the code, pointing a better approach:
import 'package:flutter/material.dart';
// Avoid displaying the warning "Name types using UpperCamelCase."
class Display extends StatefulWidget {
// Make these fields final and the constructor const
final int myId;
final String myMenu;
const Display(this.myId, this.myMenu);
#override
_DisplayState createState() => _DisplayState();
}
// Avoid displaying the warning "Name types using UpperCamelCase."
class _DisplayState extends State<Display> {
// You don't need this Future nor this initState
//
// Future myVarUsed() async {
// setState(() {
// print('myIdDsiplay: ${widget.myId}'); // null
// print('myMenuDisplay : ${widget.myMenu}'); // null
// });
// }
//
// #override
// void initState() {
// super.initState();
// myVarUsed();
// }
#override
Widget build(BuildContext context) {
return Container(
color: Colors.blue,
height: 250,
width: 250,
child: Row(
children: [
Text('My ID is : ${widget.myId}'),
Text('My menu is : ${widget.myMenu}'),
],
),
);
}
}
class Menu extends StatefulWidget {
// Avoid using mutable static fields
// static int myId;
// static String myMenu;
// To simplify, you can add a onChanged callback to
// be triggered whenever you change `myId` and `myMenu`
final void Function(int myId, String myMenu) onChanged;
const Menu({this.onChanged});
#override
_MenuState createState() => _MenuState();
}
class _MenuState extends State<Menu> {
Container scrollList() {
final PageController controller = PageController(initialPage: 1, keepPage: true, viewportFraction: 0.35);
return Container(
color: Colors.red,
height: 90,
child: PageView.builder(
scrollDirection: Axis.horizontal,
controller: controller,
itemCount: listdata.length,
physics: BouncingScrollPhysics(),
itemBuilder: (BuildContext context, int index) {
return Container(
child: gestureDetectorOntap(index),
);
},
),
);
}
// Avoid displaying the warning "Name non-constant identifiers using lowerCamelCase."
GestureDetector gestureDetectorOntap(int index) {
return GestureDetector(
onTap: () {
// Make these local variables
int myId = listdata[index].id;
String myMenu = listdata[index].menuObj;
// Call the `onChanged` callback
widget.onChanged(myId, myMenu);
// This widget is being thrown away
// display(Menu.myId, Menu.myMenu);
print('myIDMenu $myId');
print('myMenuMenu $myMenu');
},
child: Container(
alignment: AlignmentDirectional.center,
child: Text(
'${listdata[index].menuObj}',
),
),
);
}
Widget build(BuildContext context) {
return Container(
child: scrollList(),
);
}
}
// Avoid the warning "Name types using UpperCamelCase."
class ListModel {
// You can make these fields final and the constructor const
final int id;
final String menuObj;
const ListModel(this.id, this.menuObj);
}
// You can make this list const to avoid modifying it unintentionally later
const List<ListModel> listdata = [
ListModel(0, 'Menu01'),
ListModel(1, 'Menu02'),
ListModel(2, 'Menu03'),
ListModel(3, 'Menu04'),
ListModel(4, 'Menu05')
];
void main() {
runApp(MyHomePage());
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
// Create fields to store the current `myId` and current `myMenu`
int myId;
String myMenu;
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Container(
child: Column(
children: <Widget>[
// Add the `onChanged` callback here, updating this widget state
Menu(
onChanged: (newMyId, newMyMenu) {
setState(() {
myId = newMyId;
myMenu = newMyMenu;
});
}
),
// Access the current values here
Display(myId, myMenu),
],
),
),
),
);
}
}

Related

Stateful widget inside an Provider (ChangeNotifier) widget does not get updated

I have a Stateless-Provider widget along with its ChangeNotifier-model. Inside the Provider, there is a Stateful widget. When notifyListeners is called, all widgets in the stateless widget get updated, except the Stateful one. What am I missing here, and how do I go about it? Providing a simplified example here: Upon pressing the button, the expected result is First: The value is 1st, but the actual output is First: The value is 2nd
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class Model extends ChangeNotifier {
final List<ListElement> elements;
Model({required this.elements});
void add() {
elements.insert(0, ListElement(name: "First", value: "1st"));
notifyListeners();
}
}
class ListElement {
final String name;
var value;
ListElement({required this.name, required this.value});
}
class ValueWidget extends StatefulWidget {
final String value;
ValueWidget({required this.value});
#override
State<StatefulWidget> createState() => _ValueWidget(value: value);
}
class _ValueWidget extends State<ValueWidget> {
String value;
_ValueWidget({required this.value});
#override
Widget build(BuildContext context) {
return Text("The value is ${value}.");
}
}
class StatelessPage extends StatelessWidget {
final model = Model(elements: [
ListElement(name: "Second", value: "2nd"),
ListElement(name: "Third", value: "3rd")]);
#override
Widget build(BuildContext context) {
return Scaffold(
body: ChangeNotifierProvider(
create: (context) => model,
child: ConsumerWidget())
);
}
}
class ConsumerWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Consumer<Model>(builder: (context, model, _) {
return SingleChildScrollView(
child: Container(
padding: EdgeInsets.fromLTRB(10, 30, 10, 10000),
child: Column(
children: [Column(
children: model.elements.map((element) {
return Row(children: [
Text("${element.name}: "),
ValueWidget(value: element.value)
]);
}).toList(),
),
TextButton(onPressed: model.add,
child: Text("Add element to beginning")),
],
),
),
);
});
}
}
Please consider that this is simplified version of my production code, and changing the whole Provider class to a Stateful one would be difficult.
Edit: Thanks Aimen for showing the path. What finally worked was using only the index of the list elements in the Stateful wiget (ValueWidget). And fetch the data from the model. I think the reason for this is that if the Stateful-widget in independece is not affected, it will not rebuild. We need to affect the build part of the widget. Pasting the changed working code.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class Model extends ChangeNotifier {
final List<ListElement> elements;
Model({required this.elements});
void add() {
elements.insert(0, ListElement(name: "First", value: "1st"));
notifyListeners();
}
}
class ListElement {
final String name;
var value;
ListElement({required this.name, required this.value});
}
class ValueWidget extends StatefulWidget {
final int ind;
final Model model;
ValueWidget({required this.ind, required this.model});
#override
State<StatefulWidget> createState() => _ValueWidget(
ind: ind, model: model);
}
class _ValueWidget extends State<ValueWidget> {
final int ind;
final Model model;
_ValueWidget({required this.ind, required this.model});
#override
Widget build(BuildContext context) {
// Can also use Provider like this so that it does not need to be passed
// final model = Provider.of<Model>(context, listen: true);
// This is the part because of which widget is getting rebuilt
final element = model.elements[ind];
return Text("The value is ${element.value}.");
}
}
class StatelessPage extends StatelessWidget {
final model = Model(
elements: [
ListElement(name: "Second", value: "2nd"),
ListElement(name: "Third", value: "3rd")]
);
#override
Widget build(BuildContext context) {
return Scaffold(
body: ChangeNotifierProvider(
create: (context) => model,
child: ConsumerWidget())
);
}
}
class ConsumerWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Consumer<Model>(builder: (context, model, _) {
return SingleChildScrollView(
child: Container(
padding: EdgeInsets.fromLTRB(10, 30, 10, 10000),
child: Column(
children: [Column(
children:
model.elements.asMap().entries.map((ele) {
return Row(children: [
Text("${ele.value.name}: "),
ValueWidget(ind: ele.key, model: model),
]);
}).toList(),
),
TextButton(onPressed: model.add,
child: Text("Add element to beginning")),
],
),
),
);
});
}
}
you are not implementing provider in the stateful widget you are just passing a value through a parameter you need to call a provider and set the listen to true
inside the statful widget
like
var model = Model.of(context, listen = true);
List elements = model.elements;
here the elements variable will change when the elements in the provider will have a new value

Best practice to update value from another class

I am new to flutter, so please excuse my experience.
I have 2 classes, both stateful widgets.
One class contains the tiles for a listview.
Each tile class has a checkbox with a state bool for alternating true or false.
The other class (main) contains the body for creating the listview.
What I'd like to do is retrieve the value for the checkbox in the main class, and then update a counter for how many checkbboxes from the listview tiles have been checked, once a checkbox value is updated. I am wondering what the best practices are for doing this.
Tile class
class ListTile extends StatefulWidget {
#override
_ListTileState createState() => _ListTileState();
}
class _ListTileState extends State<ListTile> {
#override
Widget build(BuildContext context) {
bool selected = false;
return Container(
child: Row(
children: [Checkbox(value: selected, onChanged: (v) {
// Do something here
})],
),
);
}
}
Main Class
class OtherClass extends StatefulWidget {
#override
_OtherClassState createState() => _OtherClassState();
}
class _OtherClassState extends State<OtherClass> {
#override
Widget build(BuildContext context) {
return Container(
child: Column(
children: [
Text("Checkbox selected count <count here>"),
ListView.builder(itemBuilder: (context, index) {
// Do something to get the selected checkbox count from the listview
return ListTile();
}),
],
),
);
}
}
Hope this is you are waiting for
class OtherClass extends StatefulWidget {
#override
_OtherClassState createState() => _OtherClassState();
}
class _OtherClassState extends State<OtherClass> {
bool selected = false;
#override
void initState() {
super.initState();
}
var items = [
Animal("1", "Buffalo", false),
Animal("2", "Cow", false),
];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("title")),
body: Container(
child: ListView.builder(
itemCount: items.length,
shrinkWrap: true,
itemBuilder: (ctx, i) {
return Row(
children: [
Text(items[i].name),
ListTile(
id: items[i].id,
index: i,
)
],
);
}),
));
}
}
ListTileClass
class ListTile extends StatefulWidget {
final String? id;
final int? index;
final bool? isSelected;
const ListTile ({Key? key, this.id, this.index, this.isSelected})
: super(key: key);
#override
_ListTileState createState() => _ListTileState();
}
class _ListTileState extends State<ListTile> {
bool? selected = false;
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Container(
width: 20,
child: Checkbox(
value: selected,
onChanged: (bool? value) {
setState(() {
selected = value;
});
}));
}
}
I'd recommend using a design pattern such as BLoC or using the Provider package. I personally use the Provider Package. There are plenty of tutorials on youtube which can help get you started.

How to detect drag initiated in another widget in Flutter

Im sorry if the title seems off, I couldn't find a better way to frame it. I have a grid of containers on the screen, and I want to be able to draw on the screen selecting and dragging across the screen. I read about the GestureDetector class but it only detects gesture that starts in one widget, I can do a onPanDown, onPanUpdate,onPanEnd but that just gives me the co-ordinates of the cursor, and I didn't feel like it was the most elegant way to do it.(I might be wrong). The Block Class:
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
class Block extends StatefulWidget {
#override
_BlockState createState() => _BlockState();
}
class _BlockState extends State<Block> {
Color boxColor = Colors.white;
#override
Widget build(BuildContext context) {
return InkWell(
onTap: () {
if (boxColor == Colors.blueGrey[900])
boxColor = Colors.white;
else
boxColor = Colors.blueGrey[900];
setState(() {});
},
child: Container(
height: 20,
width: 20,
decoration: BoxDecoration(
color: boxColor,
border: Border.all(
color: Colors.grey,
width: 1,
)),
),
);
}
}
PathFinding Class: (Painting the blocks in a grid):
import 'package:flutter/material.dart';
import 'Block.dart';
class PathFinding extends StatefulWidget {
#override
_PathFindingState createState() => _PathFindingState();
}
class _PathFindingState extends State<PathFinding> {
#override
Widget build(BuildContext context) {
List<List<Widget>> grid = [
...List.generate(
40, (index) => [...List.generate(40, (index) => Block())])
];
return Scaffold(
body: Column(
// crossAxisAlignment: CrossAxisAlignment.center,
children: [
...grid.map((e) => Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [...e],
))
],
),
);
}
}
Just pass a callback as a parameter from your _PathFindingState, whenevr you are creating a Block.
First, add 2 extra params to your Block that can be passed while creating it.
class Block extends StatefulWidget {
final void onTap; // The function from the parent to be called
final int id; // An id that is unique to this Block
Block({ this.onTap, this.id });
#override
_BlockState createState() => _BlockState();
}
Then, in your _BlockState, whenever a tap is detected, call the new function to inform the Block class, which will then inform the _PathFindingState class.
InkWell(
onTap: () {
if (boxColor == Colors.blueGrey[900])
boxColor = Colors.white;
else
boxColor = Colors.blueGrey[900];
setState(() {});
widget.onTap(widget.id); // This line will call the `onTap` function that is present in the `Block`
},
Finally, in your _PathFindingState,
class _PathFindingState extends State<PathFinding> {
void onTap (int id) {
// A Block with `id` = id has been tapped,
}
#override
Widget build(BuildContext context) {
List<List<Widget>> grid = [
...List.generate(
40, (index) => [...List.generate(40,
(index) => Block(id: index, onTap: onTap) // Pass index as id and the onTap function
)])
];
This architecture can be followed for any Gesture that has been detected on any Block and you will receive the callback in the _PathFindingState class and you can do whatever you want with it.

Flutter, calling a function inside the button

I am new to flutter and I make some practices. I have a StatelessWidget called doChange and makeChange and one StatefulWidget. This class which is statefulwidget I made child of the home page of the app also. But, I think that it is unnecessary to define here. My purpose in this case is that, I want to change the state of the button make open,make closed and at the same time the text open and close will also change. I think that class changeText has not problem but in makeChange class I have some trouble with creating constructor and function to call into onPress. The states do not change. How can i solve this or is that any way to do this without function ?
class changeText extends StatelessWidget{
final doChange;
changeText({#required this.doChange});
#override
Widget build(BuildContext context){
return Container(
//some codes
//some codes
child: doChange ? Text("open") : Text("close"),
);
}
}
class makeChange extends StatelessWidget{
final changeState;
makeChange({#required this.changeState}); // I want to add constructor here lets say onPressButton
whenPressed(){ // I want to create a function with the that constructor that I have add.
}
#override
Widget build(BuildContext context){
return Container(
//some codes
//
child: Column(
children: [
MaterialButton(
//some codes
//
onPressed: () {} // Here I want to call a function when press the button.
child: changeState ? Text("make open") : Text("make close"),
),
],
),
);
}
}
class Mainarea extends StatefulWidget{
#override
_MainareaState createState() => _mainAreaState();
}
class _MainareaState extends State<Mainarea> {
bool isChange= false;
#override
Widget build(BuildContext context){
return Container(
//some codes
//
child: Column(
children: <Widget>[
changeText(doChange: !this.isChange),
makeChange(changeState: !this.isChange),
],
),
);
}
}
I just added a final Function(bool) callback as a parameter, which can be called inside from the stateless widget, and returns to the calling function. From there you can call setState
class changeText extends StatelessWidget {
final bool doChange;
changeText({#required this.doChange});
#override
Widget build(BuildContext context) {
return Container(
//some codes
//some codes
child: doChange ? Text("open") : Text("close"),
);
}
}
class makeChange extends StatelessWidget {
final bool changeState;
final Function(bool) callback;
makeChange(
{#required
this.changeState,
#required
this.callback}); // You can rename this to onPressed or whatever
#override
Widget build(BuildContext context) {
return Container(
//some codes
//
child: Column(
children: [
MaterialButton(
//some codes
//
onPressed: () => callback( changeState),
child: changeState ? Text("make close") : Text("make open"), //I had to swap these around to make the text correct
),
],
),
);
}
}
class Mainarea extends StatefulWidget {
#override
_MainareaState createState() => _MainareaState();
}
class _MainareaState extends State<Mainarea> {
bool isChange = false;
#override
Widget build(BuildContext context) {
return Container(
//some codes
//
child: Column(
children: <Widget>[
changeText(doChange: !this.isChange),
makeChange(
changeState: !this.isChange,
callback: (bool val) {
setState(() => isChange = val); //this is your function that returns and resetst the value in the parent widget
},
),
],
),
);
}
}

Flutter - select only single item in list view

In my app I am generating a ListView and items can be highlighted by tapping on them. That works fine and I also have a callback function that gives me the key for the just selected item. I can currently manually deselect the item by tapping on it again, but will ultimately take that functionality out.
My problem is that I want one and only one item to be selected at a time. In order to create the list I currently take some initial content in the form of a list, generate the tiles and add them to another list. I then use that list to create the ListView. My plan was on the callback from a new selection, run through the list of tiles and deselect them before highlighting the new chosen tile and carrying out the other functions. I have tried various methods to tell each tile to deselect itself but have not found any way to address each of the tiles. Currently I get the error:
Class 'OutlineTile' has no instance method 'deselect'.
Receiver: Instance of 'OutlineTile'
Tried calling: deselect()
I have tried to access a method within the tile class and to use a setter but neither worked so far. I am quite new to flutter so it could be something simple I am missing. My previous experience was with Actionscript where this system would have worked fine and I could access a method of an object (in this case the tile) easily as long s it is a public method.
I'd be happy to have another way to unselect the old item or to find a way to access a method within the tile. The challenge is to make the tiles show not highlighted without them being tapped themselves but when a different tile is tapped.
The code in my parent class is as follows:
class WorkingDraft extends StatefulWidget {
final String startType;
final String name;
final String currentContent;
final String currentID;
final List startContent;
WorkingDraft(
{this.startType,
this.name,
this.currentContent,
this.currentID,
this.startContent});
#override
_WorkingDraftState createState() => _WorkingDraftState();
}
class _WorkingDraftState extends State<WorkingDraft> {
final _formKey = GlobalKey<FormState>();
final myController = TextEditingController();
//String _startType;
String _currentContent = "";
String _name = "Draft";
List _startContent = [];
List _outLineTiles = [];
int _counter = 0;
#override
void dispose() {
// Clean up the controller when the widget is disposed.
myController.dispose();
super.dispose();
}
void initState() {
super.initState();
_currentContent = widget.currentContent;
_name = widget.name;
_startContent = widget.startContent;
_counter = 0;
_startContent.forEach((element) {
_outLineTiles.add(OutlineTile(
key: Key("myKey$_counter"),
outlineName: element[0],
myContent: element[1],
onTileSelected: clearHilights,
));
_counter++;
});
}
dynamic clearHilights(Key myKey) {
_outLineTiles.forEach((element) {
element.deselect(); // this throws an error Class 'OutlineTile' has no instance method 'deselect'.
Key _foundKey = element.key;
print("Element Key $_foundKey");
});
}
.......
and further down within the widget build scaffold:
child: ListView.builder(
itemCount: _startContent.length,
itemBuilder: (context, index) {
return _outLineTiles[index];
},
),
Then the tile class is as follows:
class OutlineTile extends StatefulWidget {
final Key key;
final String outlineName;
final Icon myIcon;
final String myContent;
final Function(Key) onTileSelected;
OutlineTile(
{this.key,
this.outlineName,
this.myIcon,
this.myContent,
this.onTileSelected});
#override
_OutlineTileState createState() => _OutlineTileState();
}
class _OutlineTileState extends State<OutlineTile> {
Color color;
Key _myKey;
#override
void initState() {
super.initState();
color = Colors.transparent;
}
bool _isSelected = false;
set isSelected(bool value) {
_isSelected = value;
print("set is selected to $_isSelected");
}
void changeSelection() {
setState(() {
_myKey = widget.key;
_isSelected = !_isSelected;
if (_isSelected) {
color = Colors.lightBlueAccent;
} else {
color = Colors.transparent;
}
});
}
void deselect() {
setState(() {
isSelected = false;
color = Colors.transparent;
});
}
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(top: 4.0),
child: Row(
children: [
Card(
elevation: 10,
margin: EdgeInsets.fromLTRB(10.0, 6.0, 5.0, 0.0),
child: SizedBox(
width: 180,
child: Container(
color: color,
child: ListTile(
title: Text(widget.outlineName),
onTap: () {
if (widget.outlineName == "Heading") {
Text("Called Heading");
} else (widget.outlineName == "Paragraph") {
Text("Called Paragraph");
widget.onTileSelected(_myKey);
changeSelection();
},
),
........
Thanks for any help.
Amended Code sample and explanation, that builds to a complete project, from here:
Following the advice from phimath I have created a full buildable sample of the relevant part of my project.
The problem is that the tiles in my listview are more complex with several elements, many of which are buttons in their own right so whilst phimath's solution works for simple text tiles I have not been able to get it working inside my own project. My approach is trying to fundamentally do the same thing as phimath's but when I include these more complex tiles it fails to work.
This sample project is made up of three files. main.dart which simply calls the project and passes in some dummy data in the way my main project does. working_draft.dart which is the core of this issue. And outline_tile.dart which is the object that forms the tiles.
Within working draft I have a function that returns an updated list of the tiles which should show which tile is selected (and later any other changes from the other buttons). This gets called when first going to the screen. When the tile is tapped it uses a callback function to redraw the working_draft class but this seems to not redraw the list as I would expect it to. Any further guidance would be much appreciated.
The classes are:
first class is main.dart:
import 'package:flutter/material.dart';
import 'package:listexp/working_draft.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
home: WorkingDraft(
startType: "Basic",
name: "Draft",
currentID: "anID",
startContent: [
["Heading", "New Heading"],
["Paragraph", "New Text"],
["Image", "placeholder"],
["Signature", "placeholder"]
],
));
}
}
Next file is working_draft.dart:
import 'package:flutter/material.dart';
import 'package:listexp/outline_tile.dart';
class WorkingDraft extends StatefulWidget {
final String startType;
final String name;
final String currentContent;
final String currentID;
final List startContent;
final int selectedIndex;
WorkingDraft(
{this.startType,
this.name,
this.currentContent,
this.currentID,
this.startContent,
this.selectedIndex});
#override
_WorkingDraftState createState() => _WorkingDraftState();
}
class _WorkingDraftState extends State<WorkingDraft> {
int selectedIndex;
String _currentContent = "";
String _name = "Draft";
List _startContent = [];
var _outLineTiles = [];
int _counter = 0;
int _selectedIndex;
bool _isSelected;
dynamic clearHilights(int currentIndex) {
setState(() {
_selectedIndex = currentIndex;
});
}
updatedTiles() {
if (_selectedIndex == null) {
_selectedIndex = 0;
}
_currentContent = widget.currentContent;
_name = widget.name;
_startContent = widget.startContent;
_counter = 0;
_outLineTiles = [];
_startContent.forEach((element) {
_isSelected = _selectedIndex == _counter ? true : false;
_outLineTiles.add(OutlineTile(
key: Key("myKey$_counter"),
outlineName: element[0],
myContent: element[1],
myIndex: _counter,
onTileSelected: clearHilights,
isSelected: _isSelected,
));
_counter++;
});
}
#override
Widget build(BuildContext context) {
updatedTiles();
return Scaffold(
body: Center(
child: Column(children: [
SizedBox(height: 100),
Text("Outline", style: new TextStyle(fontSize: 15)),
Container(
height: 215,
width: 300,
decoration: BoxDecoration(
border: Border.all(
color: Colors.lightGreenAccent,
width: 2,
),
borderRadius: BorderRadius.circular(2),
),
child: ListView.builder(
itemCount: _startContent.length,
itemBuilder: (context, index) {
return _outLineTiles[index];
},
),
),
]),
));
}
}
and finally is outline_tile.dart
import 'package:flutter/material.dart';
class OutlineTile extends StatefulWidget {
final Key key;
final String outlineName;
final Icon myIcon;
final String myContent;
final int myIndex;
final Function(int) onTileSelected;
final bool isSelected;
OutlineTile(
{this.key,
this.outlineName,
this.myIcon,
this.myContent,
this.myIndex,
this.onTileSelected,
this.isSelected});
#override
_OutlineTileState createState() => _OutlineTileState();
}
class _OutlineTileState extends State<OutlineTile> {
Color color;
// Key _myKey;
bool _isSelected;
#override
void initState() {
super.initState();
_isSelected = widget.isSelected;
if (_isSelected == true) {
color = Colors.lightBlueAccent;
} else {
color = Colors.transparent;
}
}
void deselect() {
setState(() {
_isSelected = widget.isSelected;
if (_isSelected == true) {
color = Colors.lightBlueAccent;
} else {
color = Colors.transparent;
}
});
}
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(top: 4.0),
child: Row(
children: [
Card(
elevation: 10,
margin: EdgeInsets.fromLTRB(10.0, 6.0, 5.0, 0.0),
child: SizedBox(
width: 180,
child: Container(
color: color,
child: ListTile(
title: Text(widget.outlineName),
onTap: () {
if (widget.outlineName == "Heading") {
Text("Called Heading");
} else if (widget.outlineName == "Paragraph") {
Text("Called Paragraph");
} else if (widget.outlineName == "Signature") {
Text("Called Signature");
} else {
Text("Called Image");
}
var _myIndex = widget.myIndex;
widget.onTileSelected(_myIndex);
deselect();
},
),
),
),
),
SizedBox(
height: 60,
child: Column(
children: [
SizedBox(
height: 20,
child: IconButton(
iconSize: 30,
icon: Icon(Icons.arrow_drop_up),
onPressed: () {
print("Move Up");
}),
),
SizedBox(height: 5),
SizedBox(
height: 20,
child: IconButton(
iconSize: 30,
icon: Icon(Icons.arrow_drop_down),
onPressed: () {
print("Move Down");
}),
),
],
),
),
SizedBox(
height: 60,
child: Column(
children: [
SizedBox(
height: 20,
child: IconButton(
iconSize: 20,
icon: Icon(Icons.add_box),
onPressed: () {
print("Add another");
}),
),
SizedBox(
height: 10,
),
SizedBox(
height: 20,
child: IconButton(
iconSize: 20,
icon: Icon(Icons.delete),
onPressed: () {
print("Delete");
}),
),
],
),
),
],
),
);
}
}
Thanks again
Instead of manually deselecting tiles, just keep track of which tile is currently selected.
I've made a simple example for you. When we click a tile, we just set the selected index to the index we clicked, and each tile looks at that to see if its the currently selected tile.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(body: Home()),
);
}
}
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
int selectedIndex;
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: 10,
itemBuilder: (context, index) {
return ListTile(
title: Text('Item: $index'),
tileColor: selectedIndex == index ? Colors.blue : null,
onTap: () {
setState(() {
selectedIndex = index;
});
},
);
},
);
}
}