Trying to create animated list but not sure how to get listKey from another script file? - flutter

I am trying to make my list animated, so when i delete task it plays animation. I watched few tutorials however i am not sure how to implement this into my code because they had the animatedlist and void deleteitem in the same script, where i am having in 2 different one.
Here is my code from the task_data script file
void removeItem(Task task) {
//removedTask = task;
final item = _tasks.remove(task);
listKey.currentState!.removeItem(
task,
(context, animation) => TaskTile(
//taskTitle: task.name,
//isChecked: task.isDone,
//checkboxCallback: (checkboxState) {
//taskData.updateTask(task);
taskTitle: task.name,
isChecked: task.isDone,
animation: animation, checkboxCallback: (bool) {},
longPressCallback: () {},
));
notifyListeners();
saveData();
}
Here is my code from task_tile script
class TaskTile extends StatelessWidget {
final bool isChecked;
final String taskTitle;
final Function(bool?) checkboxCallback;
final VoidCallback longPressCallback;
final Animation<double> animation;
TaskTile({
required this.isChecked,
required this.taskTitle,
required this.checkboxCallback,
required this.longPressCallback,
required this.animation,
});
And here is my code from tasks_list script file
#override
Widget build(BuildContext context) {
return Consumer<TaskData>(
builder: (context, taskData, child) {
return AnimatedList(
key: _listKey,
initialItemCount: taskData.tasks.length,
itemBuilder: (context, index, animation) {
return TaskTile(
animation: animation,
taskTitle: taskData.tasks[index].name,
//isChecked: Provider.of<TaskData>(context).tasks[index].isDone,
//Provider.of<TaskData>(context).tasks = task_data. we would use LHS when we did not wrap with Consumer
isChecked: taskData.tasks[index].isDone,
checkboxCallback: (checkboxState) {
HapticFeedback.mediumImpact();
taskData.updateTask(taskData.tasks[index]);
},
longPressCallback: () {
ScaffoldMessenger.of(context).showSnackBar(snackBar(taskData));
taskData.deleteTask(taskData.tasks[index]);
HapticFeedback.heavyImpact();
},
);
},
//itemCount: taskData.taskCount,
);
},
);
Would really appreciate if someone can help me with this!
EDIT
--- I am getting this error right now "The argument type 'Task' can't be assigned to the parameter type 'int'."
on the actual screen it displays red box range error and I'm not sure how to fix those

Use it as a global key to access the keys from other classes
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
//Create it as Global key
final myListKey = GlobalKey<AnimatedListState>();
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final widgets = [
Container(color: Colors.red),
Container(color: Colors.green),
Container(color: Colors.yellow),
];
int currentIndex = 0;
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: AnimatedList(
key: myListKey,
initialItemCount: widgets.length,
itemBuilder: (_, index, animation) {
return Container(
height: 100,
child: widgets[index],
);
},
),
);
}
}

Related

Flutter bloc not working after hot reload

My app using bloc/cubit to display a list of Todo items works fine until I hot reload/hot restart the application!
I have two buttons, when i click these, the cubit state is set to having 3 todo items. Additionally I have two periodic timer which sets the cubit state to having only 1 or 0 todo items again. So the number of items is constantly changing from 1 to 0 until, or if i press a button it momentarily becomes 3.
This works fine until hot reload/restart after which the buttons no longer work! The periodic changes do work however. I can only alleviate this problem by creating my ToDoBloc as a field Initializer within my "MyApp" base widget.
main.dart:
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:todo/todo_api_controller.dart';
import 'package:todo/todo_bloc.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
MyApp({Key? key}) : super(key: key);
late TodoBloc _todoBloc; //!!----IF I CREATE THE TODOBLOC HERE EVERYTHING WORKS--!!
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
_todoBloc = TodoBloc(
apiController: TodoApiController(),
);
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(_todoBloc),
);
}
}
class MyHomePage extends StatelessWidget {
TodoBloc _todoBloc;
MyHomePage(this._todoBloc, {Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: BlocProvider<TodoBloc>.value(
value: _todoBloc,
child: BlocBuilder<TodoBloc, TodoBlocState>(
builder: (context, state) {
return Builder(
builder: (context) => Column(
mainAxisSize: MainAxisSize.min,
children: [
TextButton(
onPressed: () => context.read<TodoBloc>().LoadAll(),
child: Text(
'pressme',
style: TextStyle(color: Colors.red),
)),
ListView.builder(
shrinkWrap: true,
itemCount: state.todos.length,
itemBuilder: (context, index) {
var todo = state.todos[index];
return CheckboxListTile(
value: todo.isFinished,
onChanged: (newvalue) {},
);
}),
],
));
},
),
),
floatingActionButton: FloatingActionButton(
onPressed: () async {
await _todoBloc.LoadAll();
},
),
);
}
}
todo_api_controller.dart
class TodoApiController {
List<Todo> GetAll() {
return [
Todo(id: "asfsdf", name: "this is todo", isFinished: true, finishedOn: DateTime.now()),
Todo(id: "asfsdf", name: "this is todo", isFinished: true, finishedOn: DateTime.now()),
];
}
void Delete(String id) {}
void Update(Todo todo) {}
Todo Create() {
return Todo(id: "asdfsdf");
}
}
class Todo {
final String id;
String name;
bool isFinished;
DateTime? finishedOn;
Todo({required String id, String name = "", bool isFinished = false, DateTime? finishedOn = null})
: id = id,
name = name,
isFinished = isFinished,
finishedOn = finishedOn {}
}
todo_bloc.dart
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:todo/todo_api_controller.dart';
class TodoBloc extends Cubit<TodoBlocState> {
static int numberOfInstances = 0;
int myInstance = -1;
TodoApiController _apiController;
List<Todo> todos = [];
TodoBloc({required TodoApiController apiController})
: _apiController = apiController,
super(TodoBlocState(todos: [TodoItemState(id: "asdfsdf", name: "sdfsdf", isFinished: true, finishedOn: null)])) {
numberOfInstances++;
myInstance = numberOfInstances;
Timer.periodic(Duration(seconds: 2), (s) => emit(TodoBlocState()));
Future.delayed(
Duration(seconds: 1),
() => Timer.periodic(
Duration(seconds: 2), (s) => emit(TodoBlocState(todos: [TodoItemState(id: "asdfsdf", name: "sdfsdf", isFinished: true, finishedOn: null)]))));
}
Future<void> LoadAll() async {
/* var newTodos = _apiController.GetAll();
todos.clear();
todos.addAll(newTodos);
var newState = MakeState();
emit(newState);*/
emit(TodoBlocState(todos: [
TodoItemState(id: "asdfsdf", name: "sdfsdf", isFinished: true, finishedOn: null),
TodoItemState(id: "asdfsdf", name: "sdfsdf", isFinished: true, finishedOn: null),
TodoItemState(id: "asdfsdf", name: "sdfsdf", isFinished: true, finishedOn: null),
]));
}
TodoBlocState MakeState() {
return TodoBlocState(
todos: todos
.map((e) => TodoItemState(
id: e.id,
finishedOn: e.finishedOn,
isFinished: e.isFinished,
name: e.name,
))
.toList(),
);
}
}
class TodoBlocState {
final List<TodoItemState> todos = [];
TodoBlocState({List<TodoItemState>? todos}) {
this.todos.addAll(todos ?? []);
}
}
class TodoItemState {
final String id;
final String name;
final bool isFinished;
final DateTime? finishedOn;
TodoItemState({required this.id, required this.name, required this.isFinished, required this.finishedOn});
}
I cant figure out why this is, especially with hot restart, as this should reset all application state.
EDIT: the issue appears after a hot reload(not hot restart) but cannot be fixed by hot restart
EDIT2: the issue is fixed by adding a GlobalKey() to the MyHomePage class. Though I cannot understand why. Can someone explain this to me?
This is happening because you're initializing your TodoBloc inside a build function.
Hot reload rebuilds your widgets so it triggers a new call to their build functions.
You should convert it into a StatefulWidget and initilize your TodoBloc inside the initState function:
class MyApp extends StatefulWidget {
MyApp({Key? key}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
late TodoBloc _todoBloc;
#override
void initState() {
super.initState();
_todoBloc = TodoBloc(
apiController: TodoApiController(),
);
}
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(_todoBloc),
);
}
}
You really don't need to declare and initialize your TodoBloc at the top of the widget tree then pass it all the way down. BlocProvider creates a new instance that is accessible via context.read<TodoBloc>().
Your MyApp could look like this.
class MyApp extends StatelessWidget {
MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: BlocProvider(
create: (context) => TodoBloc(apiController: TodoApiController()), // this is your bloc being created and initialized
child: MyHomePage(),
),
);
}
}
And MyHomePage could be simplified. Note the lack of BlocProvider.value and Builder. All you need is a BlocBuilder and the correct instance of TodoBloc is always accessible with context.read<TodoBloc>().
class MyHomePage extends StatelessWidget {
MyHomePage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: BlocBuilder<TodoBloc, TodoBlocState>(
builder: (context, state) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
TextButton(
onPressed: () => context.read<TodoBloc>().LoadAll(),
child: Text(
'pressme',
style: TextStyle(color: Colors.red),
),
),
ListView.builder(
shrinkWrap: true,
itemCount: state.todos.length,
itemBuilder: (context, index) {
var todo = state.todos[index];
return CheckboxListTile(
value: todo.isFinished,
onChanged: (newvalue) {},
);
},
),
],
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: () async {
await context.read<TodoBloc>().LoadAll();
},
),
);
}
}

How do I add floatingactionbutton in my ListView in Flutter dart

I want to add a floatingactionbutton in my ListPage on the bottom right corner.
I tried adding it but I am getting error or it is becoming a dead code.
An on press will be implemented on that floatingactionbutton to create a user and that will be reflected in the listview page.
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
void main() => runApp(new AdminPage());
class AdminPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Admin Dashboard',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(title: 'Admin Dashboard'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context){
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: ListPage(),
);
}
}
class ListPage extends StatefulWidget {
#override
_ListPageState createState() => _ListPageState();
}
class _ListPageState extends State<ListPage> {
Future _data;
Future getPosts() async {
var firestore = Firestore.instance;
QuerySnapshot qn = await firestore.collection("admins").getDocuments();
return qn.documents;
}
#override
Widget build(BuildContext context) {
Future getPosts() async {
var firestore = Firestore.instance;
QuerySnapshot qn = await firestore.collection("admins").getDocuments();
return qn.documents;
}
navigateToDetail(DocumentSnapshot post){
Navigator.push(context, MaterialPageRoute(builder: (context) => DetailPage(post: post,)));
}
#override
void initState(){
super.initState();
_data = getPosts();
}
return Container(
child: FutureBuilder(
future: _data,
builder: (_, snapshot){
if(snapshot.connectionState == ConnectionState.waiting){
return Center(
child: Text("Loading..."),
);
} else {
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (_, index){
return ListTile(
title: Text(snapshot.data[index].data["email"]),
onTap: () => navigateToDetail(snapshot.data[index]),
);
});
}
}),
);
}
}
class DetailPage extends StatefulWidget {
final DocumentSnapshot post;
DetailPage({this.post});
#override
_DetailPageState createState() => _DetailPageState();
}
class _DetailPageState extends State<DetailPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title : Text(widget.post.data["name"]),
),
body: Container(
child:Card(
child: ListTile(
title:Text(widget.post.data["email"]),
subtitle: Text(widget.post.data["name"]),
),
),
),
);
}
}
Image of the screen can be found below
You can add floatingActionButton argument on Scaffold
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: ListPage(),
floatingActionButton: FloatingActionButton(
onPressed: () =>{},
child: const Icon(Icons.add),
),
);
You can add FAB in listview by wrapping FloatingActionButton inside of Transform.translate:
floatingActionButton:Transform.translate(
offset: const Offset(-10, -70),
child: FloatingActionButton(
onPressed: () =>{},
child: const Icon(Icons.add),
),
),

Flutter AnimatedList with Provider Pattern

I have model which implements ChangeNotifier
class DataModel with ChangeNotifier{
List<Data> data = List<Data>();
void addData(Data data){
data.add(data);
notifyListeners();
}
}
and a ListView which listens to those changes:
class DataListView extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Consumer<DataModel>(
builder: (context, model, child) {
return ListView.builder(
itemCount: model.data.length,
itemBuilder: (context, index) {
return Text(model.data[index].value);
},
);
},
);
}
}
so far so good, when an item is added to the list in the model, the change notification triggers a rebuild of the Listview and I see the new data. But I cant wrap my head around using this with a AnimatedList instead of a ListView. Preferably id like to keep my model as it is, seeing as the animation is a concern of the ui and not of my logic.
The changenotifier always gives me a uptodate version of my data, but what i really need is a "item added" or "item removed" notification.
Is there a best practice way of doing this?
This is the result of my trial.
It's a riverpod version, but I think it's the same for providers.
There are two points.
Initialize the state in the parent widget of the widget that uses
AnimatedList.
Add / delete AnimatedList and add / delete states asynchronously by using async.
main.dart
import 'package:animatedlist_riverpod_sample/provider.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
import 'package:hooks_riverpod/all.dart';
void main() {
runApp(ProviderScope(child: MyApp()));
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: Home(),
);
}
}
class Home extends HookWidget {
const Home({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
final todoList = useProvider(todoListProvider.state);
return Scaffold(appBar: AppBar(title: Text('Todo[${todoList.length}]')), body: TodoListView());
}
}
class TodoListView extends HookWidget {
TodoListView({Key key}) : super(key: key);
final GlobalKey<AnimatedListState> _listKey = GlobalKey<AnimatedListState>();
final todoList = useProvider(todoListProvider.state);
#override
Widget build(BuildContext context) {
return AnimatedList(
key: _listKey,
initialItemCount: todoList.length,
itemBuilder: (context, index, animation) =>
_buildItem(todoList[index], animation, index, context),
);
}
Slidable _buildItem(Todo todo, Animation<double> animation, int index, BuildContext context) {
return Slidable(
actionPane: SlidableDrawerActionPane(),
child: SizeTransition(
sizeFactor: animation,
axis: Axis.vertical,
child: ListTile(title: Text(todo.description), subtitle: Text(todo.id), onTap: () => {})),
secondaryActions: <Widget>[
IconSlideAction(
caption: 'Delete',
color: Colors.red,
icon: Icons.delete,
onTap: () {
_listKey.currentState.removeItem(
index, (context, animation) => _buildItem(todo, animation, index, context),
duration: Duration(milliseconds: 200));
_removeItem(context, todo);
},
),
],
);
}
void _removeItem(BuildContext context, Todo todo) async {
await Future.delayed(
Duration(milliseconds: 200), () => context.read(todoListProvider).remove(todo));
}
}
provider.dart
import 'package:hooks_riverpod/all.dart';
final todoListProvider = StateNotifierProvider<TodoList>((ref) {
return TodoList([
Todo(id: '0', description: 'Todo1'),
Todo(id: '1', description: 'Todo2'),
Todo(id: '2', description: 'Todo3'),
]);
});
class Todo {
Todo({
this.id,
this.description,
});
final String id;
final String description;
}
class TodoList extends StateNotifier<List<Todo>> {
TodoList([List<Todo> initialTodos]) : super(initialTodos ?? []);
void add(String description) {
state = [
...state,
Todo(description: description),
];
}
void remove(Todo target) {
state = state.where((todo) => todo.id != target.id).toList();
}
}
sample repository is here.
I recently started to learn Flutter and was surprised to find that this topic isn't covered properly anywhere. I came up with two approaches which I called Basic and Advanced. Let's start from Basic. It's named like that because Provider is called within the same widget where AnimatedList is built.
class Users extends ChangeNotifier {
final _list = ['0', '1', '2', '3', '4'];
int get length => _list.length;
operator [](index) => _list[index];
int add() {
final int index = length;
_list.add('$index');
notifyListeners();
return index;
}
String removeAt(int index) {
String user = _list.removeAt(index);
notifyListeners();
return user;
}
}
class BasicApp extends StatelessWidget {
const BasicApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
home: ChangeNotifierProvider(create: (_) => Users(), child: AnimatedListDemo()));
}
}
class AnimatedListDemo extends StatelessWidget {
final GlobalKey<AnimatedListState> _listKey = GlobalKey();
AnimatedListDemo({Key? key}) : super(key: key);
void addUser(Users users) {
final int index = users.add();
_listKey.currentState!.insertItem(index, duration: const Duration(seconds: 1));
}
void deleteUser(Users users, int index) {
String user = users.removeAt(index);
_listKey.currentState!.removeItem(
index,
(context, animation) {
return SizeTransition(sizeFactor: animation, child: _buildItem(users, user));
},
duration: const Duration(seconds: 1),
);
}
Widget _buildItem(Users users, String user, [int? removeIndex]) {
return ListTile(
key: ValueKey<String>(user),
title: Text(user),
leading: const CircleAvatar(
child: Icon(Icons.person),
),
trailing: (removeIndex != null)
? IconButton(
icon: const Icon(Icons.delete),
onPressed: () => deleteUser(users, removeIndex),
)
: null,
);
}
#override
Widget build(BuildContext context) {
Users users = Provider.of<Users>(context, listen: false);
return Scaffold(
appBar: AppBar(
title: const Text('Basic AnimatedList Provider Demo'),
),
body: AnimatedList(
key: _listKey,
initialItemCount: users.length,
itemBuilder: (context, index, animation) {
return FadeTransition(
opacity: animation,
child: _buildItem(users, users[index], index),
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: () => addUser(users),
tooltip: 'Add an item',
child: const Icon(Icons.add),
),
);
}
}
Advanced approach differs in that it encapsulates AnimatedListState. I took this idea from Flutter's AnimatedList docs.
typedef RemovedItemBuilder = Widget Function(
String user, BuildContext context, Animation<double> animation);
class Users extends ChangeNotifier {
final _list = ['0', '1', '2', '3', '4'];
final GlobalKey<AnimatedListState> _listKey = GlobalKey();
final RemovedItemBuilder _removedItemBuilder;
Users(this._removedItemBuilder);
int get length => _list.length;
operator [](index) => _list[index];
GlobalKey<AnimatedListState> get listKey => _listKey;
int add() {
final int index = length;
_list.add('$index');
_listKey.currentState!.insertItem(index, duration: const Duration(seconds: 1));
notifyListeners();
return index;
}
String removeAt(int index) {
String user = _list.removeAt(index);
_listKey.currentState!.removeItem(
index,
(BuildContext context, Animation<double> animation) {
return _removedItemBuilder(user, context, animation);
},
duration: const Duration(seconds: 1),
);
notifyListeners();
return user;
}
}
class AdvancedApp extends StatelessWidget {
const AdvancedApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const MaterialApp(home: AnimatedListDemo());
}
}
class AnimatedListDemo extends StatelessWidget {
const AnimatedListDemo({Key? key}) : super(key: key);
Widget _buildItem(BuildContext context, String user, [int? removeIndex]) {
Users users = Provider.of<Users>(context, listen: false);
return ListTile(
key: ValueKey<String>(user),
title: Text(user),
leading: const CircleAvatar(
child: Icon(Icons.person),
),
trailing: (removeIndex != null)
? IconButton(
icon: const Icon(Icons.delete),
onPressed: () => users.removeAt(removeIndex),
)
: null,
);
}
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(create: (_) => Users((user, context, animation) {
return SizeTransition(sizeFactor: animation, child: _buildItem(context, user));
}), child: Scaffold(
appBar: AppBar(
title: const Text('Advanced AnimatedList Provider Demo'),
),
body: Consumer<Users>(builder: (BuildContext context, Users users, _){
return AnimatedList(
key: users.listKey,
shrinkWrap: true,
initialItemCount: users.length,
itemBuilder: (context, index, animation) {
return FadeTransition(
opacity: animation,
child: _buildItem(context, users[index], index),
);
},
);
}),
floatingActionButton: const AddButtonSeparateWidget(),
));
}
}
class AddButtonSeparateWidget extends StatelessWidget {
const AddButtonSeparateWidget({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
Users users = Provider.of<Users>(context, listen: false);
return FloatingActionButton(
onPressed: users.add,
tooltip: 'Add an item',
child: const Icon(Icons.add),
);
}
}
All code is published on Github. Now I want to elaborate a bit on your proposition of having "item added" or "item removed" notifications. From what I understand it goes against Flutter's philosophy where widget is a UI config. When a widget's state changes, Flutter diffs against its previous state and magically applies the diff to UI. That's why I didn't use "item added", "item removed" notifications in my implementations. However I think it should be possible to do because I saw a similar approach in Firestore subscription to document changes although for now I can't figure how to implement the same with Provider. Provider's documentation is kind of poor. After a careful reading I can't say how to implement partial updates with Provider. May be ProxyProvider with its update could help or may be ListenableProvider. Let me know if you could find the solution to your proposition.

setState() within StatefulWidget not working properly

The thing I'm trying to do is, to change the colour of a RawMaterialButton when the button is clicked. Read about StatefulWidget and it seemed like it should work, but for some reason it doesn't.
flutter: Another exception was thrown: setState() called in constructor: ButtonTest#1a93b(lifecycle state: created, no widget, not mounted)
ButtonTest class:
class ButtonState extends StatefulWidget {
#override
State createState() => ButtonTest();
}
class ButtonTest extends State<ButtonState> implements Cipher {
#override
String icon = '';
#override
String title = '';
bool enabled = false;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(this.title),
),
body: RawMaterialButton(
shape: CircleBorder(side: BorderSide(color: Colors.black)),
fillColor: enabled ? Colors.blue : Colors.red,
onPressed: () {
setState(() {
this.enabled = true;
});
},
padding: EdgeInsets.all(0)),
);
}
}
Cipher class:
abstract class Cipher {
String icon;
String title;
Widget build(BuildContext context);
}
getCiphers()
getCiphers() {
final List<Cipher> ciphers = new List();
ciphers.add(ButtonTest());
return ciphers;
}
Main class:
void main() => runApp(CipherTools());
class CipherTools extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'CipherTools',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: CipherScreen(
ciphers: getCiphers(),
),
);
}
}
class CipherScreen extends StatelessWidget {
final List<Cipher> ciphers;
CipherScreen({Key key, #required this.ciphers}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Ciphers'),
),
body: ListView.builder(
itemCount: ciphers.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(ciphers[index].title),
// When a user taps on the ListTile, navigate to the DetailScreen.
// Notice that we're not only creating a DetailScreen, we're
// also passing the current todo through to it!
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DetailScreen(cipher: ciphers[index]),
),
);
},
);
},
),
);
}
}
class DetailScreen extends StatelessWidget {
// Declare a field that holds the Todo
final Cipher cipher;
// In the constructor, require a Todo
DetailScreen({Key key, #required this.cipher}) : super(key: key);
#override
Widget build(BuildContext context) {
return cipher.build(context);
}
}
What am I doing wrong here?
Wrap setState() like this.
if(this.mounted) {
setState(() {
this.enabled = true;
});
}
A couple of things:
ButtonState should be called ButtonTest because this is the
StatefulWidget
ButtonTest should be called ButtonTestState because this is the State.
Then in DetailScreen, in the build() method, you could return the StatefulWidget (ButtonTest), like this:
#override
Widget build(BuildContext context) {
return ButtonTest();
}

Proper page navigation

I am trying to navigate to a page called contactView. I have made a list of contacts and I wait to navogate to a contact when I click on there name. This is what I have so far. I am stuck trying to get the navigation to work. Any help would be great.
class ContactList extends StatelessWidget {
final List<Contact> _contacts;
ContactList(this._contacts);
#override
Widget build(BuildContext context) {
return new ListView.builder(
padding: new EdgeInsets.symmetric(vertical: 8.0),
itemBuilder: (context, index) {
return new _ContactListItem(_contacts[index]);
Navigator.push(context, MaterialPageRoute(builder: (context) => viewContact())
);
},
itemCount: _contacts.length,
);
}
}
Here are few things that I can immediately point out (Problems):
onPressed is not available on ListView.builder() , you may check
here:
https://docs.flutter.io/flutter/widgets/ListView/ListView.builder.html
Navigator.push(context, MaterialPageRoute(builder: (context) => viewContact()) this won't execute because it is after return
Suggestions:
You might need to wrap your _ContactListItem() inside a
GestureDetector and implement an onTap callback
Sample Code:
class ContactList extends StatelessWidget {
final List<Contact> _contacts;
ContactList(this._contacts);
#override
Widget build(BuildContext context) {
return ListView.builder(
padding: EdgeInsets.symmetric(vertical: 8.0),
itemBuilder: (context, index) {
return GestureDetector(
onTap: () {
//TODO: Insert your navigation logic here
Navigator.of(context).push(MaterialPageRoute(
builder: (BuildContext context) =>
ContactView(_contacts[index])));
},
child: _ContactListItem(_contacts[index]),
);
},
itemCount: _contacts.length,
);
}
}
Another option could be to change the implementation of
_ContactListItem() and may be use a ListTile and implement an onTap in ListTile, you can find it here: https://docs.flutter.io/flutter/material/ListTile-class.html
You may also try to implement named routes, here is a tutorial for
that https://flutter.io/cookbook/networking/named-routes/
I hope this was helpful in someway, let me know if I misinterpreted the question.
See if the below is what you're looking for.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Contact Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Contact Demo'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final _contacts = [
Contact(name: 'John'),
Contact(name: 'Mary'),
Contact(name: 'Suzy')
];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: null,
title: const Text(
'Contact Demo',
style: const TextStyle(color: Colors.white),
),
),
body: ListView.builder(
itemCount: _contacts.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text('Contact #$index'),
onTap: () {
Navigator.of(context).push(MaterialPageRoute<void>(
builder: (BuildContext context) =>
ContactView(contact: _contacts[index]),
));
},
);
},
),
);
}
}
class Contact {
Contact({this.name});
final String name;
}
class ContactView extends StatelessWidget {
ContactView({this.contact});
final Contact contact;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(contact.name),
),
body: Center(
child: Text(contact.name),
),
);
}
}