ScopedModel - how to pass more than one model - flutter

In sample we pass the single model like this
return ScopedModel<CounterModel>(
model: CounterModel(), // only one model here
child: MaterialApp(
title: 'Scoped Model Demo',
home: CounterHome('Scoped Model Demo'),
),
);
How to pass some models instead of single model? So that to use them later this way
Widget build(BuildContext context) {
final username =
ScopedModel.of<UserModel>(context, rebuildOnChange: true).username;
final counter =
ScopedModel.of<CounterModel>(context, rebuildOnChange: true).counter;
return Text(...);
}

You can copy paste and run the full code below.
You can reference this https://newcodingera.com/scoped-model-in-flutter/
You can pass your three models and wrap them nested
Code snippet
void main() {
runApp(MyApp(
counterModel: CounterModel(),
userModel: UserModel('Brian'),
dataModel: DataModel('this is test'),
));
}
class MyApp extends StatelessWidget {
final CounterModel counterModel;
final UserModel userModel;
final DataModel dataModel;
const MyApp({
Key key,
#required this.counterModel,
#required this.userModel,
#required this.dataModel,
}) : super(key: key);
#override
Widget build(BuildContext context) {
// At the top level of our app, we'll, create a ScopedModel Widget. This
// will provide the CounterModel to all children in the app that request it
// using a ScopedModelDescendant.
return ScopedModel<DataModel>(
model: dataModel,
child: ScopedModel<UserModel>(
model: userModel,
child: ScopedModel<CounterModel>(
model: counterModel,
Working demo
Full code
import 'package:flutter/material.dart';
import 'package:scoped_model/scoped_model.dart';
void main() {
runApp(MyApp(
counterModel: CounterModel(),
userModel: UserModel('Brian'),
dataModel: DataModel('this is test'),
));
}
class MyApp extends StatelessWidget {
final CounterModel counterModel;
final UserModel userModel;
final DataModel dataModel;
const MyApp({
Key key,
#required this.counterModel,
#required this.userModel,
#required this.dataModel,
}) : super(key: key);
#override
Widget build(BuildContext context) {
// At the top level of our app, we'll, create a ScopedModel Widget. This
// will provide the CounterModel to all children in the app that request it
// using a ScopedModelDescendant.
return ScopedModel<DataModel>(
model: dataModel,
child: ScopedModel<UserModel>(
model: userModel,
child: ScopedModel<CounterModel>(
model: counterModel,
child: MaterialApp(
title: 'Scoped Model Demo',
home: CounterHome('Scoped Model Demo'),
),
),
),
);
}
}
// Start by creating a class that has a counter and a method to increment it.
//
// Note: It must extend from Model.
class CounterModel extends Model {
int _counter = 0;
int get counter => _counter;
void increment() {
// First, increment the counter
_counter++;
// Then notify all the listeners.
notifyListeners();
}
}
class UserModel extends Model {
String _username;
UserModel(String username) : _username = username;
String get username => _username;
set username(String newName) {
_username = newName;
notifyListeners();
}
}
class DataModel extends Model {
String _data;
DataModel(String data) : _data = data;
String get data => _data;
set data(String newData) {
_data = newData;
notifyListeners();
}
}
class CounterHome extends StatelessWidget {
final String title;
CounterHome(this.title);
#override
Widget build(BuildContext context) {
final counter =
ScopedModel.of<CounterModel>(context, rebuildOnChange: true).counter;
final userModel = ScopedModel.of<UserModel>(context, rebuildOnChange: true);
final dataModel = ScopedModel.of<DataModel>(context, rebuildOnChange: true);
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('${userModel.username} pushed the button this many times:'),
Text('${dataModel.data} From data model'),
// Create a ScopedModelDescendant. This widget will get the
// CounterModel from the nearest parent ScopedModel<CounterModel>.
// It will hand that CounterModel to our builder method, and
// rebuild any time the CounterModel changes (i.e. after we
// `notifyListeners` in the Model).
Text('$counter', style: Theme.of(context).textTheme.display1),
Padding(
padding: const EdgeInsets.all(8.0),
child: RaisedButton(
child: Text('Change Username'),
onPressed: () {
userModel.username = 'Suzanne';
},
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: RaisedButton(
child: Text('Change Data'),
onPressed: () {
dataModel.data = 'data changed';
},
),
)
],
),
),
// Use the ScopedModelDescendant again in order to use the increment
// method from the CounterModel
floatingActionButton: ScopedModelDescendant<CounterModel>(
builder: (context, child, model) {
return FloatingActionButton(
onPressed: model.increment,
tooltip: 'Increment',
child: Icon(Icons.add),
);
},
),
);
}
}

Related

List not populating in Flutter

I am trying to have a form when I fill it out will populate a ListView, but can't seem to get the list to popluate with any values.
I am using the following to have a bottom navigation:
class _AppState extends State<App> {
int _currentIndex = 0;
final List<Widget> body = [
AddNewStudent(),
StudentList(),
];
In a file that has the form looks like this:
class StudentClass {
String kidFirstName;
String kidLastName;
DateTime dateOfBirth;
int totalAttedance = 0;
int attedanceAtRank = 0;
StudentClass(
{required this.kidFirstName,
required this.kidLastName,
required this.dateOfBirth});
}
class AddNewStudent extends StatefulWidget {
#override
AddStudentScreen createState() => AddStudentScreen();
}
class AddStudentScreen extends State<AddNewStudent> {
List<StudentClass> studentList = [];
void addStudent(StudentClass newStudent) {
setState(() {
studentList.add(newStudent);
});
}
final formKey = GlobalKey<FormState>();
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.all(20.0),
child: SingleChildScrollView(
child: Column(
children: [
Form(
key: formKey,
child: Column(
children: [
kidsFirstNameFormField(),
kidLastNameFormField(),
kidDateofBirth(),
submitButton(),
],
),
),
],
)));
}
Widget submitButton() {
return ElevatedButton(
child: Text('Create New Student Profile'),
onPressed: () {
if (formKey.currentState?.validate() ?? false) {
formKey.currentState?.save();
StudentClass newStudent = StudentClass(
kidFirstName: kidFirstName,
kidLastName: kidLastName,
dateOfBirth: dateOfBirth,
);
addStudent(newStudent);
formKey.currentState?.reset();
}
},
);
}
The listview builder is in its own file:
class StudentList extends StatelessWidget {
#override
Widget build(BuildContext context) {
List<StudentClass> studentList = [];
return Scaffold(
appBar: AppBar(
title: Text('Student List'),
),
body: StudentListState(
studentList: studentList,
),
);
}
}
class StudentListState extends StatefulWidget {
final List<StudentClass> studentList;
StudentListState({required this.studentList});
#override
_StudentListState createState() => _StudentListState();
}
class _StudentListState extends State<StudentListState> {
void addStudent(StudentClass student) {
setState(() {
widget.studentList.add(student);
});
}
#override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.all(20.0),
child: ListView.builder(
itemCount: widget.studentList.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(widget.studentList[index].kidFirstName),
subtitle: Text(widget.studentList[index].kidLastName),
trailing: Text(widget.studentList[index].dateOfBirth.toString()),
);
},
));
}
}
I am pretty stuck on figuring out how to pass the information over to the list to populate. I have gotten it to build with no errors but somehow I know I am not passing it correctly. I know I might have an extra list in here.
You are updating the studentList of the AddStudentScreen widget. And in the ListView you are rendering the studentList from StudentList widget which is a different variable and is always empty.
Also, you are initialising studentList inside the build function which means that on every setState() studentList will be initialised to an empty list.
Seems like you want to use the same data in multiple widgets. In such cases consider using a state manager.
For you scenario, I'd recommend you use stream_mixin.
Example:
Create student service using StoreService from stream_mixin package.
class StudentModel extends BaseModel { // NOTICE THIS
String kidFirstName;
String kidLastName;
DateTime dateOfBirth;
int totalAttedance = 0;
int attedanceAtRank = 0;
StudentModel({
required this.kidFirstName,
required this.kidLastName,
required this.dateOfBirth,
required String id,
}) : super(id: id);
}
class StudentService extends StoreService<StudentModel> { // NOTICE THIS
StudentService._();
static StudentService store = StudentService._(); // Just creating a singleton for StudentService.
}
To add student data in the store (note, this can be done anywhere in the app):
const student = new StudentModel(
// add student data here
)
StudentService.store.add(student);
Render this list of students
StreamBuilder<StudentModel>(
stream: StudentService.store.onChange,
builder: (context, snapshot) {
if (snapshot.data == null) {
return Text("No student added yet.");
}
return ListView.builder(
itemCount: StudentService.store.values.length,
itemBuilder: (context, index) {
const student = StudentService.store.values[index];
return ListTile(
title: Text(student.kidFirstName),
subtitle: Text(student.kidLastName),
trailing: Text(student.dateOfBirth.toString()),
);
},
)
},
)
Now, every time you add student data using StudentService.store.add(), it will emit an event which your SteamBuilder with stream: StudentService.store.onChange is listening and will update the UI to show the updated list.
This will also eliminate the necessity of StatefulWidget. Which means you can use only StatelessWidget unless otherwise you require StatefulWidget for something else.

Navigating to another page in Flutter with and without arguments

I've only been coding in Flutter for a few weeks now and I would like to know if it is possible just to navigate to a page using named routes that has received arguments from another page? The main objective is to navigate to the Cart Screen from two different pages where one passes an argument while the other doesn't. Here is my code below to explain my question:
This is the first part of the code which navigates to the cart screen after passing arguments id and quantity
class ItemDetailsState extends State<ItemDetails> {
int quantity = 1; //quantity
#override
Widget build(BuildContext context) {
final routes =
ModalRoute.of(context)!.settings.arguments as Map<String, dynamic>;
final id = routes["id"]; //id
return Scaffold(
......
InkWell(
onTap: () {
Navigator.of(context).pushNamed('/cart-screen', arguments: { //This navigates to the cart screen passing arguments id and quantity
'id': routes["id"],
'quantity': quantity,
});
Provider.of<CartItemProvider>(context, listen: false)
.addItems(id, name, restaurantName, price, quantity);
},
);
}
}
This is the Cart Screen that receives the arguments and filters data from a Provider Class:
class CartScreen extends State<CartScreenState> {
#override
Widget build(BuildContext context) {
final routes =
ModalRoute.of(context)!.settings.arguments as Map<String, dynamic>;
final id = routes['id']; //Received Arguments
final quantity = routes['quantity']; //Received Arguments
final provider =
Provider.of<PopularDishesProvider>(context).getProductById(id); //Provider that filters the data as per ID
My idea is to navigate to the Cart Screen page from another page like this but it throws the below error:
class HomeScreenState extends State<HomeScreen> {
Widget build(BuildContext context) {
return Scaffold(
..............
body: Row(
children: [
InkWell(
onTap: () => Navigator.of(context)
.pushReplacementNamed('/cart-screen'), //Navigate to the Cart Screen
child: const Icon(
Icons.shopping_cart_outlined,
color: Colors.grey,
size: 30,
),
),
InkWell(
onTap: () {},
child: const Icon(
Icons.notifications_none_outlined,
color: Colors.grey,
size: 30,
),
)
],
)
The method '[]' was called on null.
Receiver: null
Tried calling: []("id")
The above error I believe is owing to the fact that I'm trying to just navigate to '/cart-screen' without passing any argument in the HomeScreenState widget. I need suggestions to know if there's any way to get around this?
The route is declared in the main.dart file as it should like
routes : {
'/cart-screen': (context) => CartScreen(),
}
You can check null value using
#override
Widget build(BuildContext context) {
var arguments3 = ModalRoute.of(context)!.settings.arguments;
var routes=
arguments3!=null? arguments3 as Map<String, dynamic>:{};
final id = routes['id']??0; //Received Arguments
final quantity = routes['quantity']??0; //Received Arguments
final provider =
Provider.of<PopularDishesProvider>(context).getProductById(id);
We can pass argument with the help of argument property in pushnamed method
Navigator.pushNamed(context, AppRoutes.Page1,
arguments: {"name": "lava", "body": "chi"});
Receive value
var arguments3 = ModalRoute.of(context)!.settings.arguments;
var arguments2 =
arguments3!=null? arguments3 as Map<String, dynamic>:{};
May like this
SAmple Code
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return MaterialApp(
initialRoute: "/",
routes: {
AppRoutes.home: (context) => Home(),
AppRoutes.Page1: (context) => Page1(),
},
title: _title,
// home: ,
);
}
}
class Home extends StatelessWidget {
const Home({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("title")),
body: const Center(
child: MyStatelessWidget(),
),
);
}
}
var _color = Colors.black;
var _value = 0.0;
class MyStatelessWidget extends StatefulWidget {
const MyStatelessWidget({Key? key}) : super(key: key);
#override
State<MyStatelessWidget> createState() => _MyStatelessWidgetState();
}
class _MyStatelessWidgetState extends State<MyStatelessWidget> {
#override
Widget build(BuildContext context) {
return Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
onPressed: () {
Navigator.pushNamed(context, AppRoutes.Page1);
},
child: Text("Without Argument")),
ElevatedButton(
onPressed: () {
Navigator.pushNamed(context, AppRoutes.Page1,
arguments: {"name": "lava", "body": "chi"});
},
child: Text("With Argument")),
],
),
);
}
#override
void initState() {}
}
class Page1 extends StatelessWidget {
const Page1({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
var arguments3 = ModalRoute.of(context)!.settings.arguments;
var arguments2 =
arguments3!=null? arguments3 as Map<String, dynamic>:{};
// {"name": "nodata", "body": "no data"};
return Material(
child: Center(
child: Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Text(arguments2["name"] ?? "Nodata",
style: TextStyle(fontSize: 30)),
Text(
arguments2["body"] ?? "No DAta",
style: TextStyle(fontSize: 30),
),
],
),
),
),
);
}
}
class AppRoutes {
static String failed = "/page2";
static String Page1 = "/page1";
static String home = "/";
}
your design is a little confusing.
if you are trying to get the ID and Quantity in the Cart-screen, then why do you want to navigate to it without the arguments?
any how, I guess you have a use case where you want to do different thing if the arguments are not passed. then the only thing you need is to check if the arguments are null. right?
#override
Widget build(BuildContext context) {
final routes =
ModalRoute.of(context)!.settings.arguments as Map<String, dynamic>;
if (routes != null) {
final id = routes['id']; //Received Arguments
final quantity = routes['quantity']; //Received Arguments
final provider =
Provider.of<PopularDishesProvider>(context).getProductById(id);
} else {
// do the things here when no argument is passed.
}

Problem with events in BLoC Flutter. I always call two events instead of one

I return the state with the TodoLoadedState list in each block method. But when I call the block event in onPressed, the list itself is not returned and I have to add a second call to the block method todoBloc.add(LoadTodos()); But that's not correct. Ideas need to trigger 1 event but to perform 2 actions, the second action is to update the list. Thanks in advance!
todo_bloc
class TodoBloc extends Bloc<TodoEvent, TodoState> {
final TodoRepository todoRepository;
TodoBloc(this.todoRepository) : super(TodoEmptyState()) {
on<LoadTodos>((event, emit) async {
emit(TodoLoadingState());
try {
final List<Todo> _loadedTodoList = await todoRepository.getAllTodos();
emit(TodoLoadedState(loadedUser: _loadedTodoList));
} catch (_) {
emit(TodoErrorState());
}
});
on<CreateTodos>((event, emit) async {
// Todo todo = Todo(description: event.task, isDone: false);
await todoRepository.insertTodo(event.todo);
final List<Todo> _loadedTodoList = await todoRepository.getAllTodos();
emit(TodoLoadedState(loadedUser: _loadedTodoList));
});
on<DeleteTodos>((event, emit) async {
await todoRepository.deleteTodo(event.id);
final List<Todo> _loadedTodoList = await todoRepository.getAllTodos();
emit(TodoLoadedState(loadedUser: _loadedTodoList));
});
on<UpdateTodos>((event, emit) async {
await todoRepository.updateTodo(event.todo);
final List<Todo> _loadedTodoList = await todoRepository.getAllTodos();
emit(TodoLoadedState(loadedUser: _loadedTodoList));
});
}
}
todo_list
class TodoList extends StatelessWidget {
#override
Widget build(BuildContext context) {
final TodoBloc todoBloc = context.read<TodoBloc>();
return BlocBuilder<TodoBloc, TodoState>(builder: (context, state) {
if (state is TodoEmptyState) {
return const Center(
child: Text(
'No Todo',
style: TextStyle(fontSize: 20.0),
),
);
}
if (state is TodoLoadingState) {
return const Center(child: CircularProgressIndicator());
}
if (state is TodoLoadedState) {
return ListView.builder(
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: state.loadedUser.length,
itemBuilder: (context, index) => ListTile(
title: Column(children: [
Text('${state.loadedUser[index].description}'),
Text('${state.loadedUser[index].id}'),
]),
trailing: IconButton(
onPressed: () {
todoBloc.add(DeleteTodos(id: state.loadedUser[index].id));
todoBloc.add(LoadTodos());
},
home_page
class HomePage extends StatelessWidget {
final todoRepository = TodoRepository();
#override
Widget build(BuildContext context) {
return BlocBuilder<TodoBloc, TodoState>(builder: (context, state) {
return Scaffold(
appBar: AppBar(
title: const Text('Flutter Todos'),
),
body: SingleChildScrollView(
child: Column(
children: [
TodoList(),
],
),
),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.add, size: 32, color: Colors.white),
onPressed: () {
final TodoBloc todoBloc = context.read<TodoBloc>();
final _todoDescriptionFromController = TextEditingController();
showModalBottomSheet(
context: context,
builder: (builder) {
return Padding(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom),
child: Container(
color: Colors.transparent,
child: Container(
todo_state
abstract class TodoState extends Equatable {
const TodoState();
#override
List<Object> get props => [];
}
class TodoLoadingState extends TodoState {}
class TodoEmptyState extends TodoState {}
class TodoLoadedState extends TodoState {
List<dynamic> loadedUser;
TodoLoadedState({required this.loadedUser});
}
class TodoErrorState extends TodoState {}
todo_event
abstract class TodoEvent extends Equatable {
const TodoEvent();
#override
List<Object> get props => [];
}
class LoadTodos extends TodoEvent {}
class CreateTodos extends TodoEvent {
final Todo todo;
const CreateTodos(this.todo);
}
class UpdateTodos extends TodoEvent {
final Todo todo;
const UpdateTodos(this.todo);
}
class DeleteTodos extends TodoEvent {
final int id;
const DeleteTodos({required this.id});
}
class QueryTodo extends TodoEvent {}
event onPressed, everywhere you have to use 2 events to load the updated list
todoBloc.add(UpdateTodos(updateTodo));
todoBloc.add(LoadTodos());
This is the culprit:
abstract class TodoState extends Equatable {
const TodoState();
#override
List<Object> get props => [];
}
You are extending Equatable in TodoState and passing an empty list to props. When other states such as TodoLoadedState extend TodoState they inherit Equatable as well and the empty props.
If you're using Equatable make sure to pass all properties to the
props getter.
This is from bloc faq. Right now all instances of your TodoLoadedState are considered equal. Doesn't matter if you have a TodoLoadedState with hundreds of loadedUser or a TodoLoadedState with none. They are both considered equal and only the first time you pass a new TodoLoadedState the BlocBuilder will update. The consequent ones have no effect since BlocBuilder thinks it is the same as previous one. The reason your LoadTodos event causes a rebuild is that first you emit TodoLoadingState() and then in case of success TodoLoadedState(loadedUser: _loadedTodoList). This alternating between two different states makes it work.
So either don't use Equatable or make sure to pass all the properties to props.
class TodoLoadedState extends TodoState {
final List<dynamic> loadedUser;
TodoLoadedState({required this.loadedUser});
#override
List<Object?> get props => [loadedUser];
}

Delete specific widget | Flutter & Riverpod

As shown in the image, I'm trying to have a list of dice where I can add or delete a die. I've tried StateProvider, ChangeNotifier, and StateNotifier. Each one doesn't seem to work as I expect it to. I'm trying to make a provider that contains a list of dieWidgets, but I can't figure out how to remove a specific die when I longpress on it. The image shows a popup menu to delete it, that's the long-term goal, but just a longpress delete would be good for now. Thoughts on how to approach this?
Code
main.dart
class DiceNotifier extends ChangeNotifier {
List<DieWidget> dice = [];
void add() {
dice.add(DieWidget());
notifyListeners();
}
void removeDie(int id) {
// FIXME: Unable to delete a die based on id
print(id);
notifyListeners();
}
}
final diceProvider = ChangeNotifierProvider((_) {
return DiceNotifier();
});
class MyHomePage extends ConsumerWidget {
#override
Widget build(BuildContext context, ScopedReader watch) {
final dice = watch(diceProvider).dice;
return Scaffold(
appBar: AppBar(
title: Text("Dice"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
...dice,
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
context.read(diceProvider).add();
},
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
die_widget.dart
class DieWidget extends StatefulWidget {
#override
_DieWidgetState createState() => _DieWidgetState();
}
class _DieWidgetState extends State<DieWidget> {
int value = 0;
int id = 0;
#override
Widget build(BuildContext context) {
return FlatButton(
child: Text(
'$value',
),
onPressed: () {
setState(() {
value++;
id++;
});
// context.read(dieProvider).increment();
},
onLongPress: () {
final dice = context.read(diceProvider);
dice.removeDie(id);
// print(this.value);
},
);
}
}
One solution would be to define a parameter value in the DiceWidget class:
class DiceWidget extends StatefulWidget {
const DiceWidget({ Key key, this.value }) : super(key: key);
int value;
#override
_DiceWidgetState createState() => _DiceWidgetState();
}
And access this data from the DiceWidget:
class DiceWidget extends StatefulWidget {
#override
_DiceWidgetState createState() => _DiceWidgetState();
}
class _DiceWidgetState extends State<DiceWidget> {
#override
Widget build(BuildContext context) {
return FlatButton(
child: Text(
widget.value.toString() ?? '',
),
onLongPress: () {
final dice = context.read(diceProvider);
dice.removeDice(widget.value);
// print(widget.value);
},
);
}
}
In the DiceNotifier class, I'd recommend to implement the dices array as a List<int>:
List<int> dices = [];
Therefore, the addDice() and removeDice() functions will be, respectively:
class DiceNotifier extends ChangeNotifier {
List<int> dices = [];
void addDice() {
dices.add(dices.length);
notifyListeners();
}
void removeDice(int id) {
dices.remove(id);
print(id);
notifyListeners();
}
}
To make the example work, we need to modify the MyHomePage Column children as well, to build the list of DiceWidgets:
...dices.map((d) => DiceWidget(value: d)).toList(),
The whole example will then be:
main.dart:
class DiceNotifier extends ChangeNotifier {
List<int> dices = [];
void addDice() {
dices.add(dices.length);
notifyListeners();
}
void removeDice(int id) {
dices.remove(id);
print(id);
notifyListeners();
}
}
final diceProvider = ChangeNotifierProvider((_) {
return DiceNotifier();
});
class MyHomePage extends ConsumerWidget {
#override
Widget build(BuildContext context, ScopedReader watch) {
final dices = watch(diceProvider).dices;
return Scaffold(
appBar: AppBar(
title: Text("Dice"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
...dices.map((d) => DiceWidget(value: d)).toList(),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
context.read(diceProvider).addDice();
},
child: Icon(Icons.add),
),
);
}
}
dice_widget.dart:
class DiceWidget extends StatefulWidget {
#override
_DiceWidgetState createState() => _DiceWidgetState();
}
class _DiceWidgetState extends State<DiceWidget> {
#override
Widget build(BuildContext context) {
return FlatButton(
child: Text(
widget.value.toString() ?? '',
),
onLongPress: () {
final dice = context.read(diceProvider);
dice.removeDice(widget.value);
print(widget.value);
},
);
}
}

How to reuse a stateful widget in flutter

I have a following state full widget. I need to reuse it as it is by just changing two variables id and collectionName. Generally I would extract a widget, but in this case I am modifying variable firstName which wont let me extract the widget.
class IndividualSignupPage1 extends StatefulWidget {
static final id = 'idIndividualSignupPage1';
final collectionName = 'Individual';
#override
_IndividualSignupPage1State createState() => _IndividualSignupPage1State();
}
class _IndividualSignupPage1State extends State<IndividualSignupPage1> {
String firstName;
DateTime birthDate;
final firestoreObj = Firestore.instance;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: GeneralAppBar(
appBar: AppBar(),
),
body: Container(
child: Column(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: <Widget>[
TextField(
onChanged: (value) {
this.firstName = value;
},
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
Expanded(
child: Card(
child: ListTile(
title: Text(
this.birthDate == null
? 'Birthdate'
: '${this.birthDate.year}-${this.birthDate.month}-${this.birthDate.day}',
),
onTap: () {
DatePicker.showDatePicker(
context,
initialDateTime: this.birthDate,
onConfirm: (newDate, listOfIndexes) {
setState(() {
this.birthDate = newDate;
});
},
);
},
),
),
),
],
),
WFullWidthButton(
name: 'Save',
onPressedFunc: () async {
// save all info to firestore db
firestoreObj.collection(widget.collectionName).document('xyz').setData({
'firstName': this.firstName,
'birthDate': this.birthDate,
}, merge: true);
},
),
]),
),
);
}
}
Thanks
You can pass the arguments to the Class IndividualSignupPage1 and then use it in its corresponding state class _IndividualSignupPage1State with the property "widget." like,
// pass the arguments from another class.
class IndividualSignupPage1 extends StatefulWidget {
final String id;
final String collectionName;
IndividualSignupPage1(this.id,this.collectionName);
#override
_IndividualSignupPage1State createState() => _IndividualSignupPage1State();
}
Let say you want to use id and collectionName in its corresponding state class _IndividualSignupPage1State you can access it using "widget" property like,
appBar: AppBar(title: Text(widget.id)),
**OR**
appBar: AppBar(title: Text(widget.collectionName)),
Note: you can only access the widget property inside functions/methods only.
Create IndividualSignupPage1 constructor and pass data with constructor arguments.
class IndividualSignupPage1 extends StatefulWidget {
final String id;
final String collectionName;
IndividualSignupPage1(this.id,this.collectionName);