I'm working on a planner app that has a screen with multiple widgets('Monday','Tuesday',etc).When I tap on a widget, I should be able to use TextField on pop up screen and navigate text to the widget I tapped. The issue now is that provider navigates the text to all widgets at the same time and not to only one I tapped. How could I solve that? Appreciate your help
This is a planner screen
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:my_planner_app/weekday_card.dart';
class PlannerScreen extends StatefulWidget {
static const String id = 'planner_screen';
@override
_PlannerScreenState createState() => _PlannerScreenState();
}
class _PlannerScreenState extends State<PlannerScreen>
with SingleTickerProviderStateMixin {
AnimationController controller;
Animation animation;
@override
void initState() {
super.initState();
controller =
AnimationController(duration: Duration(seconds: 3), vsync: this);
animation = ColorTween(begin: Colors.grey[800], end: Colors.white)
.animate(controller);
controller.forward();
controller.addListener(() {
setState(() {});
});
}
Widget build(BuildContext context) {
var size = MediaQuery.of(context).size;
final double itemHeight = (size.height - 24) / 2;
final double itemWidth = size.width / 2;
return Scaffold(
backgroundColor: Color(0xFFcf9e9f),
body: Container(
child: GridView(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
childAspectRatio: (itemWidth / itemHeight),
),
children: <Widget>[
WeekDayCard(
text: '',
),
WeekDayCard(text: 'Monday'),
WeekDayCard(text: 'Tuesday'),
WeekDayCard(text: 'Wednesday'),
WeekDayCard(text: 'Thursday'),
WeekDayCard(text: 'Friday'),
WeekDayCard(text: 'Saturday'),
WeekDayCard(text: 'Sunday'),
WeekDayCard(text: 'Notes'),
],
),
),
);
}
}
This is associated widget
import 'package:flutter/material.dart';
import 'package:my_planner_app/screens/addPlan_screen.dart';
import 'package:provider/provider.dart';
import 'package:my_planner_app/widgets/plan_widget.dart';
class WeekDayCard extends StatelessWidget {
WeekDayCard({@required this.text, this.name});
final String name;
final String text;
@override
Widget build(BuildContext context) {
return Consumer<MyProvider>(builder: (context, myProvider, child) {
return Card(
color: Color(0xFFFEEFCD),
elevation: 10,
child: Column(
children: [
Text(text),
Text(Provider.of<MyProvider>(context).name),
Expanded(
child: InkWell(
onTap: () {
showModalBottomSheet(
backgroundColor: Color(0xFFFEEFCD),
context: context,
builder: (context) => AddPlanScreen(),
);
},
),
),
],
),
);
});
}
}
This is associated pop up AddScreen
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:my_planner_app/widgets/plan_widget.dart';
class AddPlanScreen extends StatelessWidget {
static String name;
@override
Widget build(BuildContext context) {
return Column(
children: [
Expanded(
child: TextFormField(
onChanged: (text) {
name = text;
},
decoration: InputDecoration(
border: InputBorder.none,
),
minLines: 10,
maxLines: 30,
autocorrect: false,
),
),
FlatButton(
onPressed: () {
print(name);
Provider.of<MyProvider>(context, listen: false).setName(name);
},
color: Colors.blue,
),
],
);
}
}
Provider
import 'package:flutter/material.dart';
class MyProvider extends ChangeNotifier {
String _name = '';
String get name => _name;
void setName(String newString) {
_name = newString;
print(_name);
notifyListeners();
}
}
ChangeNotifierProvider placed before MaterialApp
void main() {
runApp(MyPlanner());
}
class MyPlanner extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => MyProvider(),
child: MaterialApp(
theme: ThemeData(fontFamily: 'IndieFlower'),
initialRoute: WelcomeScreen.id,
routes: {
WelcomeScreen.id: (context) => WelcomeScreen(),
RegisterScreen.id: (context) => RegisterScreen(),
LogInScreen.id: (context) => LogInScreen(),
PlannerScreen.id: (context) => PlannerScreen(),
},
),
);
}
}
Your ChangeNotifier
should keep a Map<String, String>
instead of a String
, one entry per weekday.
class MyProvider extends ChangeNotifier {
Map<String, String> _names = {};
String name(String key) => _names[key];
void setName(String key, String newString) {
_names[key] = newString;
notifyListeners();
}
}
Then, you will need the following changes:
Instead of Text(Provider.of<MyProvider>(context).name)
, use the key text
to get the name
for the day: Text(Provider.of<MyProvider>(context).name(text) ?? '')
.
When you open the modal bottom sheet, pass the weekday name: AddPlanScreen(weekdayName: text)
.
class WeekDayCard extends StatelessWidget {
WeekDayCard({@required this.text, this.name});
final String name;
final String text;
@override
Widget build(BuildContext context) {
return Consumer<MyProvider>(builder: (context, myProvider, child) {
return Card(
color: Color(0xFFFEEFCD),
elevation: 10,
child: Column(
children: [
Text(text),
Text(Provider.of<MyProvider>(context).name(text) ?? ''),
Expanded(
child: InkWell(
onTap: () {
showModalBottomSheet(
backgroundColor: Color(0xFFFEEFCD),
context: context,
builder: (context) => AddPlanScreen(weekdayName: text),
);
},
),
),
],
),
);
});
}
}
StatefulWidget
instead of a StatelessWidget
with a static variable.weekdayName
weekdayName
as a key: Provider.of<MyProvider>(context, listen: false).setName(widget.weekdayName, name);
class AddPlanScreen extends StatefulWidget {
final String weekdayName;
const AddPlanScreen({Key key, this.weekdayName}) : super(key: key);
@override
_AddPlanScreenState createState() => _AddPlanScreenState();
}
class _AddPlanScreenState extends State<AddPlanScreen> {
String name;
@override
Widget build(BuildContext context) {
return Column(
children: [
Expanded(
child: TextFormField(
onChanged: (text) {
name = text;
},
decoration: InputDecoration(
border: InputBorder.none,
),
minLines: 10,
maxLines: 30,
autocorrect: false,
),
),
ElevatedButton(
onPressed: () {
Provider.of<MyProvider>(context, listen: false)
.setName(widget.weekdayName, name);
},
child: Text('UPDATE'),
),
],
);
}
}
In this Solution, I will use Riverpod instead of Provider. Both packages have been authored by Remi ROUSSELET. Riverpod comes in several flavors, my preference goes for hooks_riverpod.
I kept rather the same structure:
MyPlanner
is the MaterialApp
. I encapsulated inside Riverpod's ProviderScope
PlannerScreen
is the main screen. It is now a StatelessWidget
. It is also responsive showing a grid of 4x2 or 2x4 depending on the orientationWeekdayCard
is a HookWidget
, it takes a weekday
and listens to the planProvider
AddPlanScreen
is a HookWidget
. This allows to maintain a TextEditingController without needing a StatefulWidget
. It also changes the state of the planProvider
using a context.read
What about the provider?
final planProvider = StateProvider.family<String, int>((ref, weekday) => '');
It's a simple StateProvider using the .family
provider modifier. (more info)
This allows us to listen and modify the plan for a specific weekday:
final String plan = useProvider(planProvider(weekday)).state;
context.read(planProvider(weekday)).state = plan;
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
void main() {
runApp(MyPlanner());
}
class MyPlanner extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ProviderScope(
child: MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(fontFamily: 'IndieFlower'),
initialRoute: PlannerScreen.id,
routes: {
// WelcomeScreen.id: (context) => WelcomeScreen(),
// RegisterScreen.id: (context) => RegisterScreen(),
// LogInScreen.id: (context) => LogInScreen(),
PlannerScreen.id: (context) => PlannerScreen(),
},
),
);
}
}
// SCREENS
class PlannerScreen extends StatelessWidget {
static const String id = 'planner_screen';
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color(0xFFD04E43),
body: LayoutBuilder(
builder: (_, constraints) {
final mainAxisCount =
MediaQuery.of(context).orientation == Orientation.landscape
? 2
: 4;
final crossAxisCount = mainAxisCount == 2 ? 4 : 2;
final aspectRatio =
constraints.biggest.aspectRatio * mainAxisCount / crossAxisCount;
return GridView.count(
crossAxisCount: crossAxisCount,
childAspectRatio: aspectRatio,
children: weekdayNames.keys
.map((weekday) => WeekdayCard(weekday: weekday))
.toList(),
);
},
),
);
}
}
// WIDGETS
class WeekdayCard extends HookWidget {
final int weekday;
WeekdayCard({@required this.weekday});
@override
Widget build(BuildContext context) {
final plan = useProvider(planProvider(weekday)).state;
return InkWell(
onTap: () {
showModalBottomSheet(
backgroundColor: Color(0xFFAFBDB8),
barrierColor: Colors.black38,
context: context,
builder: (context) => AddPlanScreen(weekday: weekday),
);
},
child: Card(
color: Color(0xFFAFBDB8),
elevation: 10,
child: Column(
children: [
Text(weekdayNames[weekday]),
Text(plan),
],
),
),
);
}
}
class AddPlanScreen extends HookWidget {
final int weekday;
const AddPlanScreen({Key key, this.weekday}) : super(key: key);
void submit(BuildContext context, String plan) {
context.read(planProvider(weekday)).state = plan;
Navigator.pop(context);
}
@override
Widget build(BuildContext context) {
final _controller = useTextEditingController(text: '');
return Container(
padding: EdgeInsets.all(16.0),
alignment: Alignment.center,
child: Column(
children: [
Text('What are you planning for ${weekdayNames[weekday]}?'),
const SizedBox(height: 16.0),
TextFormField(
controller: _controller,
decoration: InputDecoration(
enabledBorder: OutlineInputBorder(
borderRadius: new BorderRadius.circular(25.0),
borderSide: BorderSide(
color: Colors.black45,
width: 2.0,
),
),
focusedBorder: OutlineInputBorder(
borderRadius: new BorderRadius.circular(25.0),
borderSide: BorderSide(
color: Color(0xFFD04E43),
width: 2.0,
),
),
),
autofocus: true,
onEditingComplete: () => submit(context, _controller.text),
),
const SizedBox(height: 16.0),
ElevatedButton(
style: ElevatedButton.styleFrom(
primary: Color(0xFF548279),
onPrimary: Colors.white,
),
onPressed: () => submit(context, _controller.text),
child: Text('UPDATE'),
),
],
),
);
}
}
// PROVIDERS
final planProvider = StateProvider.family<String, int>((ref, weekday) => '');
// DOMAIN
const weekdayNames = {
0: 'Notes',
DateTime.monday: 'Monday',
DateTime.tuesday: 'Tuesday',
DateTime.wednesday: 'Wednesday',
DateTime.thursday: 'Thursday',
DateTime.friday: 'Friday',
DateTime.saturday: 'Saturday',
DateTime.sunday: 'Sunday',
};