How can i hide day from cupertino date picker - dart

i need to pic only year and month from the date picker, so how can i hide day from date picker.
CupertinoDatePicker(
initialDateTime: DateTime.now(),
onDateTimeChanged: (DateTime newdate) {
print(newdate);
widget.card.expDateTime = newdate.toString();
dateCnt.text = newdate.toString().split(" ")[0];
},
minimumYear: DateTime.now().year,
minimumDate: DateTime.now(),
mode: CupertinoDatePickerMode.date,
)

I had this problem, tried the package flutter_cupertino_date_picker but it looks like it don't have the hability to format only month and year excluding day, so you need to program on it to extend the capabilities. To me seemed more logical to change the build in CupertinoDatePicker that comes with Flutter, what I did was copy all the content of '/Users/your_user_name/developer/flutter/packages/flutter/lib/src/cupertino/date_picker.dart' in another file in my local envirronment, I called cupertino_picker_extended.dart, then (because I wanted a quick way) on line 1182 I changed: Text(localizations.datePickerDayOfMonth(day),... for Text('',...
Then where you need to use the customed Picker call it like:
import 'package:local_app/ui/widgets/cupertino_picker_extended.dart' as CupertinoExtended;
and use it:
CupertinoExtended.CupertinoDatePicker(
onDateTimeChanged: (DateTime value) {
setDate('${value.month}/${value.year}', setDateFunction,
section, arrayPos);
},
initialDateTime: DateTime.now(),
mode: CupertinoExtended.CupertinoDatePickerMode.date,
),
This is the result:
Hope it help someone and save the time I been looking for a easy and quick solution for my problem.

you have to look at flutter_cupertino_date_picker package this to do so. you can avoid picking date from user.
like below example i edited as you wish. i hope that it help you to achieve what you want.
import 'package:flutter/material.dart';
import 'package:flutter_cupertino_date_picker/flutter_cupertino_date_picker.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Date Picker Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
String _datetime = '';
int _year = 2018;
int _month = 11;
String _lang = 'en';
String _format = 'yyyy-mm';
bool _showTitleActions = true;
TextEditingController _langCtrl = TextEditingController();
TextEditingController _formatCtrl = TextEditingController();
#override
void initState() {
super.initState();
_langCtrl.text = 'zh';
_formatCtrl.text = 'yyyy-mm';
DateTime now = DateTime.now();
_year = now.year;
_month = now.month;
}
/// Display date picker.
void _showDatePicker() {
final bool showTitleActions = false;
DatePicker.showDatePicker(
context,
showTitleActions: _showTitleActions,
minYear: 1970,
maxYear: 2020,
initialYear: _year,
initialMonth: _month,
confirm: Text(
'custom ok',
style: TextStyle(color: Colors.red),
),
cancel: Text(
'custom cancel',
style: TextStyle(color: Colors.cyan),
),
locale: _lang,
dateFormat: _format,
onChanged: (year, month,date) {
debugPrint('onChanged date: $year-$month');
if (!showTitleActions) {
_changeDatetime(year, month);
}
},
onConfirm: (year, month,date) {
_changeDatetime(year, month);
},
);
}
void _changeDatetime(int year, int month) {
setState(() {
_year = year;
_month = month;
_datetime = '$year-$month';
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Date Picker Demo'),
),
body: Container(
padding: EdgeInsets.symmetric(horizontal: 16.0),
child: Column(
children: <Widget>[
// show title actions checkbox
Row(
children: <Widget>[
Text(
'Show title actions',
style: TextStyle(fontSize: 16.0),
),
Checkbox(
value: _showTitleActions,
onChanged: (value) {
setState(() {
_showTitleActions = value;
});
},
)
],
),
// Language input field
TextField(
controller: _langCtrl,
keyboardType: TextInputType.url,
decoration: InputDecoration(
labelText: 'Language',
hintText: 'en',
hintStyle: TextStyle(color: Colors.black26),
),
onChanged: (value) {
_lang = value;
},
),
// Formatter input field
TextField(
controller: _formatCtrl,
keyboardType: TextInputType.url,
decoration: InputDecoration(
labelText: 'Formatter',
hintText: 'yyyy-mm-dd / yyyy-mmm-dd / yyyy-mmmm-dd',
hintStyle: TextStyle(color: Colors.black26),
),
onChanged: (value) {
_format = value;
},
),
// Selected date
Container(
margin: EdgeInsets.only(top: 40.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text(
'Selected Date:',
style: Theme.of(context).textTheme.subhead,
),
Container(
padding: EdgeInsets.only(left: 12.0),
child: Text(
'$_datetime',
style: Theme.of(context).textTheme.title,
),
),
],
),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _showDatePicker,
tooltip: 'Show DatePicker',
child: Icon(Icons.date_range),
),
);
}
}

There has been updates on the flutter_cupertino_date_picker package. You can now specify a date format
DatePicker.showDatePicker(context,
maxDateTime: DateTime.now(),
dateFormat:'MMMM-yyyy'
);

This is the way to go
Widget datetime() {
return CupertinoDatePicker(
initialDateTime: DateTime.now(),
onDateTimeChanged: (DateTime newdate) {
print(newdate);
},
minimumYear: 2010,
maximumYear: 2030,
mode: CupertinoDatePickerMode.date,
);
}

Related

Shared Preferences in Flutter cannot save and read List

I was using some shared preferences to read and save lists but all I got is an error that goes as follows:
Launching lib/main.dart on iPhone 7 in debug mode...
Xcode build done. 4.1s
Tried calling: getString("t")
#0 Object.noSuchMethod (dart:core/runtime/libobject_patch.dart:50:5)
#1 ShareUtils.get
package:todolist2019/ShareUtils.dart:22
<asynchronous suspension>
#2 HomeScreenState.getTaskTitle
package:todolist2019/home.dart:38
#3 _AsyncAwaitCompleter.start (dart:async/runtime/libasync_patch.dart:49:6)
#4 HomeScreenState.getTaskTitle
package:todolist2019/home.dart:35
#5 HomeScreenState.build.<anonymous closure>
package:todolist2019/home.dart:97
#6 SliverChildBuilderDelegate.build
package:flutter/…/widgets/sliver.dart:398
Here is my code for home.dart, my home screen
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'dart:async';
import 'package:flutter/services.dart';
import './additem.dart';
import './ShareUtils.dart';
class HomeScreen extends StatefulWidget {
#override
State<StatefulWidget> createState() {
// TODO: implement createState
return HomeScreenState();
}
}
Timer timer;
DateTime now = DateTime.now();
String formattedTime = DateFormat('kk:mm').format(now);
String formattedDate = DateFormat('EEE d MMM').format(now);
class HomeScreenState extends State<HomeScreen> {
void changeTimeAndSetPref() {
setState(() {
DateTime now = DateTime.now();
formattedTime = DateFormat('kk:mm').format(now);
formattedDate = DateFormat('EEE d MMM').format(now);
});
}
Future getTaskTitle(index) async {
shareUtils = new ShareUtils();
shareUtils.Instance();
await shareUtils.get("title"[index]);
}
void initState() {
super.initState();
// Add listeners to this class
timer = Timer.periodic(Duration(seconds: 1), (Timer t) => changeTimeAndSetPref());
}
#override
Widget build(BuildContext context) {
// TODO: implement build
SystemChrome.setEnabledSystemUIOverlays([SystemUiOverlay.bottom]);
return Scaffold(
floatingActionButton: FloatingActionButton.extended(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => addItem()),
);
},
icon: Icon(Icons.add),
label: Text("Add Item"),
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
body: Column(
children: <Widget>[
Center(
//set the correct sizes
child: Card(
child: Column(
children: <Widget>[
Text(
formattedTime,
style: TextStyle(
fontSize: 50.0,
),
),
Text(
formattedDate,
style: TextStyle(
fontSize: 35.0,
),
),
Text(
"You have impending tasks",
style: TextStyle(
fontSize: 25.0,
),
)
],
),
),
),
Expanded(
child: ListView.builder(
itemBuilder: (context, index) {
var title = getTaskTitle(index).toString();
var detail = taskTitleList[index];
EdgeInsets.all(16.0);
return ListTile(
title: Text(
title,
style: TextStyle(fontSize: 20.0),
),
subtitle: Text(
detail,
style: TextStyle(fontSize: 15.0),
),
onTap: () {
final snackBar = SnackBar(
content: Text('Item Removed'),
duration: Duration(seconds: 1),
);
setState(() {
taskTextList.removeAt(index);
taskTitleList.removeAt(index);
Scaffold.of(context).showSnackBar(snackBar);
});
},
);
},
itemCount: taskTextList.length,
),
)
],
),
);
}
}
And here is my code for additem.dart
import 'package:flutter/material.dart';
import './home.dart';
import './ShareUtils.dart';
class addItem extends StatefulWidget {
#override
State<StatefulWidget> createState() {
// TODO: implement createState
return addItemState();
}
}
ShareUtils shareUtils;
var TaskTextField;
var TaskDetailField;
var taskTextList = [];
var taskTitleList = [];
var TaskIsImportant = false;
class addItemState extends State<addItem> {
#override
Widget build(BuildContext context) {
// TODO: implement build
void saveTask (key, value) async {
await shareUtils.set(key, value);
}
return Scaffold(
floatingActionButton: FloatingActionButton.extended(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => HomeScreen()),
);
setState(()async {
if (TaskIsImportant) {
taskTextList.add("❗$TaskTextField");
taskTitleList.add("$TaskDetailField");
saveTask("title", taskTextList);
} else {
taskTextList.add("$TaskTextField");
taskTitleList.add("$TaskDetailField");
saveTask("title", taskTextList);
}
});
},
label: Text("Add Task"),
icon: Icon(Icons.add),
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
body: Flex(
direction: Axis.vertical,
children: <Widget>[
Flexible(
child: Container(
child: Column(
children: <Widget>[
Card(
child: Column(
children: <Widget>[
Center(
child: Text(
"Welcome!",
style: TextStyle(fontSize: 50.0),
),
),
Center(
child: Text(
"Enter your task below",
style: TextStyle(fontSize: 25.0),
),
),
],
),
),
Container(
child: TextField(
decoration: InputDecoration(
hintText: "Enter title of task to be added",
hintStyle: TextStyle(fontSize: 20.0)),
onChanged: (taskTextField) {
setState(() {
TaskTextField = taskTextField;
print(TaskTextField);
});
},
),
margin: EdgeInsets.all(16.0),
),
Container(
child: TextField(
decoration: InputDecoration(
hintText: "Enter detail of task to be added",
hintStyle: TextStyle(fontSize: 20.0)),
onChanged: (taskDetailField) {
setState(() {
TaskDetailField = taskDetailField;
print(TaskDetailField);
});
},
),
margin: EdgeInsets.all(16.0),
),
CheckboxListTile(
title: Text(
"Important",
style: TextStyle(fontSize: 25.0),
),
activeColor: Colors.blue,
value: TaskIsImportant,
onChanged: (val) {
setState(() {
TaskIsImportant = !TaskIsImportant;
print(TaskIsImportant);
});
},
),
],
),
),
)
],
));
}
}
I hope someone can help me with this error. I am running on Flutter 1.2.1. Thanks in advance!
PS: I have implemented the Akio's code and I still got errors but lesser. I have also added the first 6 lines of error message.
You can add "shared_preferences: ^0.4.0" at pubspec.yaml.
And Packages get.
And you make DartFile (Ex: Filename is ShareUtils
import 'package:shared_preferences/shared_preferences.dart';
import 'dart:async';
class ShareUtils {
static ShareUtils _instance;
SharedPreferences ShareSave;
factory ShareUtils() => _instance ?? new ShareUtils._();
ShareUtils._();
void Instatce() async{
ShareSave = await SharedPreferences.getInstance();
}
Future<bool> set(key, value) async{
return ShareSave.setString(key, value);
}
Future<String> get(key) async{
return ShareSave.getString(key);
}
}
And main.dart
class MyApp extends StatelessWidget {
static ShareUtils shareUtils;
#override
Widget build(BuildContext context) {
ThemeData mainTheme = new ThemeData(
primaryColor : Color.fromRGBO(20, 42, 59, 1),
buttonColor: Color.fromRGBO(0, 132, 255, 1),
accentColor: Color.fromRGBO(31, 60, 83, 1)
);
shareUtils = new ShareUtils();
shareUtils.Instatce();
MaterialApp mainApp = new MaterialApp(
title: "Your app name",
theme: mainTheme,
home: new SplashPage(),
debugShowCheckedModeBanner: true,
routes: <String, WidgetBuilder>{
"HomePage": (BuildContext context) => new HomePage(),
},
);
return mainApp;
}
}
And you can Use this at anywhere
Before to use this, add import main.dart at header
GET:
Future NextPage() async {
MyApp.shareUtils.get("token").then((token) {
print(token);
if (token == null || token == "") {
Navigator.of(context).popAndPushNamed("RegisterPage");
} else {
Navigator.of(context).popAndPushNamed("HomePage");
}
});
}
SET:
void UserInfo(code, token) async{
await MyApp.shareUtils.set("token", token);
await MyApp.shareUtils.set("code", code);
await Navigator.of(context).pushNamed("HomePage");
}
I hope help you. Thank you
In your case MyApp.shareUtils.set("state", statelist.join("#"))
And get use statelist = token.split("#");

How to pass down data from Stateful classes to another Stateful class that exists in another file?

I'm having trouble passing the data that's been filled in a textformfields and selected in a dropdown menu.
I'm trying to use the Map function to pass down String values so that I can also pass down all types of values in the future (ex. int, bool, double etc.), however it's not working so I need someone to check it out.
main.dart
import 'package:flutter/material.dart';
import 'package:workoutapp/auth/auth.dart';
import 'package:workoutapp/auth/root_page.dart';
import 'package:workoutapp/inheritedWigets/auth_provider.dart';
void main(List<String> args) {
runApp(
WorkoutManager(),
);
}
class WorkoutManager extends StatelessWidget {
#override
Widget build(BuildContext context) {
return AuthProvider(
auth: Auth(),
child: MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Workout Manager',
home: RootPage(),
theme: ThemeData(
primaryColor: Colors.indigo,
primarySwatch: Colors.indigo,
accentColor: Colors.indigoAccent,
hintColor: Colors.indigo,
brightness: Brightness.dark,
),
),
);
}
}
HomePage
import 'package:flutter/material.dart';
import 'package:workoutapp/inheritedWigets/auth_provider.dart';
import './profile_account_page.dart';
import './routines_create_page.dart';
import '../objects/Routines/routines_manager.dart';
import '../tools/custom_drawer.dart';
class HomePage extends StatelessWidget {
final VoidCallback onSignedOut;
final List<Map<String, String>> routines;
HomePage({Key key, this.onSignedOut, this.routines}) : super(key: key);
void _signedOut(BuildContext context) async {
try {
var auth = AuthProvider.of(context).auth;
await auth.signOut();
onSignedOut();
} catch (e) {
print(e);
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Workout Manager', style: TextStyle(color: Colors.white)),
centerTitle: false,
actions: <Widget>[
FlatButton(
child: Text('Logout'),
onPressed: () {
return _signedOut(context);
},
),
IconButton(
icon: Icon(Icons.account_box),
tooltip: 'Profile Account',
color: Colors.white,
onPressed: () {
return Navigator.push(context,
MaterialPageRoute(builder: (BuildContext context) {
return ProfileAccountPage();
}));
},
),
],
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
Navigator.push(context,
MaterialPageRoute(builder: (BuildContext context) {
return RoutinesPageCreate();
}));
},
),
body: RoutinesManager(),
drawer: CustomDrawer(),
);
}
}
RoutineManager
import 'package:flutter/material.dart';
import 'package:workoutapp/objects/routines/routines.dart';
class RoutinesManager extends StatefulWidget {
final Map<String, String> startingRoutine;
RoutinesManager({this.startingRoutine});
#override
_RoutinesManagerState createState() => _RoutinesManagerState();
}
class _RoutinesManagerState extends State<RoutinesManager> {
List<Map<String, String>> _routines = [];
#override
void initState() {
if (widget.startingRoutine != null) {
_routines.add(widget.startingRoutine);
}
super.initState();
}
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Expanded(
child: Routines(_routines),
)
],
);
}
}
RoutinesCreatePage
import 'package:flutter/material.dart';
import 'package:workoutapp/pages/home_page.dart';
class RoutinesPageCreate extends StatefulWidget {
#override
_RoutinesPageCreateState createState() => _RoutinesPageCreateState();
}
class _RoutinesPageCreateState extends State<RoutinesPageCreate> {
final formKey = GlobalKey<FormState>();
List<Map<String, String>> _routines = [];
String _routineName, _routineDescription;
var _routineNameController = TextEditingController();
var _routineDescriptionController = TextEditingController();
List<DropdownMenuItem<String>> _dropdownListBodyPartMenuItem = [];
List<String> _dropdownListBodyPart = [
'Chest',
'Back',
'Leg',
'Shoulder',
'Abs',
];
String _selectedBodyPart;
List<DropdownMenuItem<String>> _dropdownListDayOfWeekMenuItem = [];
List<String> _dropdownListDayOfWeek = [
'Monday',
'Tuesday',
'Wednesday',
'Thursday',
'Friday',
'Saturday',
'Sunday',
];
String _selectedDayOfWeek;
void loadBodyPartData() {
_dropdownListBodyPartMenuItem = [];
_dropdownListBodyPartMenuItem = _dropdownListBodyPart.map((val) {
return DropdownMenuItem<String>(
child: Text(val),
value: val,
);
}).toList();
}
void loadDayOfWeekData() {
_dropdownListDayOfWeekMenuItem = [];
_dropdownListDayOfWeekMenuItem = _dropdownListDayOfWeek.map((val) {
return DropdownMenuItem<String>(
child: Text(val),
value: val,
);
}).toList();
}
final _scaffoldState = GlobalKey<ScaffoldState>();
void _showSnakBarReset() {
_scaffoldState.currentState.showSnackBar(
SnackBar(
backgroundColor: Theme.of(context).accentColor,
content: Text('Showing SnackBar TEST'),
),
);
}
void _showSnakBarCreateWorkoutRoutine() {
_scaffoldState.currentState.showSnackBar(
SnackBar(
backgroundColor: Theme.of(context).accentColor,
content: Text('Workout Routine has been created'),
),
);
}
void _addRoutine(Map<String, String> routine) {
setState(() {
_routines.add(routine);
});
}
#override
Widget build(BuildContext context) {
loadBodyPartData();
loadDayOfWeekData();
return Scaffold(
key: _scaffoldState,
appBar: AppBar(
title: Text('Create Routines'),
),
body: Container(
padding: EdgeInsets.all(15.0),
child: Form(
key: formKey,
child: ListView(children: buildInputs() + buildCreateButtons()),
),
),
);
}
List<Widget> buildInputs() {
TextStyle textStyle = Theme.of(context).textTheme.title;
return [
TextFormField(
controller: _routineNameController,
validator: (value) {
if (value.length > 20) {
return 'Not a valid Routine Name';
}
},
onSaved: (value) {
return _routineName = value;
},
decoration: InputDecoration(
labelStyle: textStyle,
labelText: 'Routine Name',
hintText: 'Enter the Routine Name for this day',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(5.0),
))),
Padding(padding: EdgeInsets.all(7.0)),
TextFormField(
controller: _routineDescriptionController,
validator: (value) {
if (value.length > 50) {
return 'Invalid: The Description must be 50 characters or less.';
}
},
onSaved: (value) {
return _routineDescription = value;
},
decoration: InputDecoration(
labelStyle: textStyle,
labelText: 'Description',
hintText: 'Enter the description of the Routine.',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(5.0),
))),
Padding(padding: const EdgeInsets.all(7.0)),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
DropdownButtonHideUnderline(
child: DropdownButton(
value: _selectedBodyPart,
items: _dropdownListBodyPartMenuItem,
hint: Text('Select Body Part', style: textStyle),
onChanged: (value) {
setState(() {
_selectedBodyPart = value;
});
})),
Padding(
padding: const EdgeInsets.all(1.0),
),
DropdownButtonHideUnderline(
child: DropdownButton(
value: _selectedDayOfWeek,
items: _dropdownListDayOfWeekMenuItem,
hint: Text('Select Day of Week', style: textStyle),
onChanged: (value) {
setState(() {
_selectedDayOfWeek = value;
});
},
),
),
Padding(
padding: const EdgeInsets.all(4.0),
)
],
),
];
}
List<Widget> buildCreateButtons() {
return [
Padding(
padding: const EdgeInsets.all(5.0),
child: Row(
children: <Widget>[
Expanded(
child: RaisedButton(
textColor: Theme.of(context).primaryColorDark,
color: Theme.of(context).accentColor,
child: Text('Create Workout Routine'),
onPressed: () {
if (formKey.currentState.validate()) {
_showSnakBarCreateWorkoutRoutine();
formKey.currentState.save();
_addRoutine({
'routineName': 'Chest Workout',
'description': 'Heavy',
'bodyPart': 'Chest',
'week': 'Monday',
});
Navigator.push(context,
MaterialPageRoute(builder: (BuildContext context) {
return HomePage();
}));
} else {
return null;
}
}),
),
Expanded(
child: RaisedButton(
textColor: Theme.of(context).primaryColorLight,
color: Theme.of(context).primaryColorDark,
child: Text('Reset'),
onPressed: () {
setState(() {
_showSnakBarReset();
formKey.currentState.reset();
_selectedBodyPart = null;
_selectedDayOfWeek = null;
});
},
),
),
],
),
),
];
}
}
Routines
import 'package:flutter/material.dart';
import 'package:workoutapp/objects/routines/routines_detail.dart';
class Routines extends StatelessWidget {
final List<Map<String, String>> routines;
Routines(this.routines);
Widget _buildRoutinesItem(BuildContext context, int index) {
TextStyle textStyle = Theme.of(context).textTheme.title;
return Expanded(
child: Card(
margin: EdgeInsets.all(5.0),
child: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(5.0),
child: Text(routines[index]['routineName'], style: textStyle)),
Padding(
padding: const EdgeInsets.all(5.0),
child: Text(routines[index]['description'], style: textStyle)),
Padding(
padding: const EdgeInsets.all(5.0),
child: Text(routines[index]['bodyPart'], style: textStyle)),
Padding(
padding: const EdgeInsets.all(5.0),
child: Text(routines[index]['week'], style: textStyle)),
Padding(
padding: const EdgeInsets.all(5.0),
child: ButtonBar(
alignment: MainAxisAlignment.center,
children: <Widget>[
FlatButton(
child: Text('Details'),
onPressed: () {
return Navigator.push(context,
MaterialPageRoute(builder: (BuildContext context) {
return RoutinesDetail(
routines[index]['routineName'],
routines[index]['description'],
routines[index]['bodyPart'],
routines[index]['week']);
}));
},
)
],
),
)
],
),
),
);
}
Widget _buildRoutinesList(context) {
TextStyle textStyle = Theme.of(context).textTheme.title;
Widget routinesCards = Container(
child: Container(
child: Center(
child: Text("No routines found, please add some.", style: textStyle),
),
),
);
if (routines.length > 0 || routines.length <= 7) {
ListView.builder(
itemBuilder: _buildRoutinesItem,
itemCount: routines.length,
);
}
return routinesCards;
}
#override
Widget build(BuildContext context) {
return _buildRoutinesList(context);
}
}
RoutineDetailPage
import 'package:flutter/material.dart';
class RoutinesDetail extends StatelessWidget {
final String routineName, description, bodyPart, week;
RoutinesDetail(this.routineName, this.description, this.bodyPart, this.week);
#override
Widget build(BuildContext context) {
TextStyle textStyle = Theme.of(context).textTheme.title;
return Scaffold(
appBar: AppBar(
title: Text(routineName),
centerTitle: true,
),
body: Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(5.0),
child: Text(routineName, style: textStyle)),
Padding(
padding: const EdgeInsets.all(5.0),
child: Text(description, style: textStyle)),
Padding(
padding: const EdgeInsets.all(5.0),
child: Text(bodyPart, style: textStyle)),
Padding(
padding: const EdgeInsets.all(5.0),
child: Text(week, style: textStyle)),
Container(
padding: EdgeInsets.all(5.0),
child: RaisedButton(
child: Text('Delete'),
onPressed: () {
Navigator.pop(context);
},
),
),
],
),
),
);
}
}
As you can see, I'm trying to separate the code into multiple files as much as possible, so it's more "readable" and make it easy for myself to make changes to the code whenever I have to in the future.
The problem is, it's quite spit up, I don't understand how to use the data and pass it down or up to the pages or widgets as there are multiple stateful and stateless widgets that are suppose to work together to make this app possible.
You'll notice on the HomePage file (StatelessWidget), I'm trying to show the Scaffold body argument with the RoutinesManager StatefulWidget, which is in a different file. At the same time in the HomePage file, I have a Scaffold floatingActionButton argument that will take you to the RoutinesCreatePage StatefulWidget to create a List of Cards (StatelessWidget) using the ListView.builder(). However, no Card gets created under the HomePage after the "Create Workout Routine" RaisedButton gets pressed in the RoutinesCreatePage and no data gets passed.
Can someone please help me out here as I am totally clueless. Also, I'm fairly a beginner regarding flutter/dart so a solution with a relatively easy to understand explanation would be very helpful.
Note: I do have other files that contribute to this app, however I don't think they're part of the problem so I left them out intentionally.
If more information is needed, please do let me know.
Thanks you!
it looks like you misunderstand what state in Flutter is. To explain in short, state is the internal status/data/... that belongs that that specific widget. StatefulWidget has state to determine if UI should be re-rendered on its own state change. External widgets never know about other widgets' states.
So it means, any state change happening inside RoutinesCreatePage widget, only that RoutinesCreatePage knows and reacts. Unless, you inform other widgets to know something has changed.
Alright, so talking about navigation, it works like a stack structure. HomePage trigger a push to RoutinesCreatePage, then to return, you need to pop, not another push.
Here a quick fix for your code, you can try.
HomePage
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
handleNewRoutine(); <--- this is to handle navigation and retrieve returning data from pop
},
),
Future handleNewRoutine() async {
var newRoutine = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => RoutinesPageCreate());
)
if (newRoutine == null) {
// nothing returns from RoutinesPageCreate widget
// so do nothing then
} else {
// add to routine list
// and trigger list re-rendering
setState(() {
this.routines.add(newRoutine);
});
}
}
RoutinesCreatePage: when clicking submit button, just populate all data from input fields, make object model and pop to return data to where this widget was pushed.
onPressed: () {
var newRoutine = .... // populate from UI to create new Routine model object.
Navigator.pop(context, newRoutine);
}
Also, take time to read the navigation guide from official Flutter documentation. It is very detailed on this part. https://flutter.io/docs/cookbook/navigation/returning-data
Some additional comments to your code:
in RoutinesCreatePage you don't need to know application level state, I mean _routines variable is unnecessary. You only need one object to store new routine to pop back to HomePage.
in Routines, this method Widget _buildRoutinesList(context) having unused ListView creation.
if (routines.length > 0 || routines.length <= 7) {
ListView.builder(
itemBuilder: _buildRoutinesItem,
itemCount: routines.length,
);
}

How to test Cupertino Picker in Flutter

I'm writing widget testing the Cupertino Picker for the different values chosen by the use. I can't find any good tutorial. I followed this https://github.com/flutter/flutter/blob/master/packages/flutter/test/cupertino/picker_test.dart but this won't work for my case. In my case when the user chooses the value from the picker the test case should check whether the user chooses the correct value or default value.
Cupertino Picker code :
List<String> ages1 = ["-- select --"];
List<String> ages2 = List<String>.generate(
45, (int index) => (21 + index).toString(),
growable: false);
List<String> ages = [ages1, ages2].expand((f) => f).toList();
picker.dart:
Widget _buildAgePicker(BuildContext context) {
final FixedExtentScrollController scrollController =
FixedExtentScrollController(initialItem: _selectedAgeIndex);
return GestureDetector(
key: Key("Age Picker"),
onTap: () async {
await showCupertinoModalPopup<void>(
context: context,
builder: (BuildContext context) {
return _buildBottomPicker(
CupertinoPicker(
key: Key("Age picker"),
scrollController: scrollController,
itemExtent: dropDownPickerItemHeight,
backgroundColor: Theme.of(context).canvasColor,
onSelectedItemChanged: (int index) {
setState(() {
_selectedAgeIndex = index;
ageValue = ages[index];
if (ageValue == S.of(context).pickerDefaultValue) {
ageDividerColor = Theme.of(context).errorColor;
errorText = S.of(context).pickerErrorMessage;
ageDividerWidth = 1.2;
} else {
ageDividerColor = Colors.black87;
errorText = "";
ageDividerWidth = 0.4;
}
});
},
children: List<Widget>.generate(ages.length, (int index) {
return Center(
child: Text(ages[index]),
);
}),
),
);
},
);
},
child: _buildMenu(
<Widget>[
Text(
S.of(context).Age,
style: TextStyle(fontSize: 17.0),
),
Text(
ages[_selectedAgeIndex],
),
],
),
);
}
Widget _buildMenu(List<Widget> children) {
return Container(
decoration: BoxDecoration(
color: Theme.of(context).canvasColor,
),
height: 44.0,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: SafeArea(
top: false,
bottom: false,
child: DefaultTextStyle(
style: const TextStyle(
color: Colors.black,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: children,
),
),
),
),
);
}
Widget _buildBottomPicker(Widget picker) {
return Container(
height: dropDownPickerSheetHeight,
padding: const EdgeInsets.only(top: 6.0),
color: Theme.of(context).canvasColor,
child: DefaultTextStyle(
style: const TextStyle(
color: Colors.black,
fontSize: 22.0,
),
child: GestureDetector(
key: Key("picker"),
onTap: () {},
child: SafeArea(
top: false,
child: picker,
),
),
),
);
}
test code :
testWidgets("picker test",(WidgetTester tester)async{
await tester.tap(find.byKey(Key("Age Picker")));
await tester.drag(find.byKey(Key("Age Picker")), Offset(0.0,70.0));
await tester.pumpAndSettle();
expect(ages[1], "21");
});
I used a similar example in my golden test. I modified it a little in order to fit your case. However, the most important part is calling both methods inclusive: fling and drag. If you call only one of them it won't work. At least in my case, that was what happened.
testWidgets("cupertino picker test", (WidgetTester tester) async{
// Find the gesture detector that invoke the cupertino picker
final gestureDetectorFinder = find.byKey(Key('Age Picker'));
await tester.tap(gestureDetectorFinder);
await tester.pump();
// Find the default option (the first one)
final ageFinder = find.text('21').last;
expect(ageFinder, findsOneWidget);
// Apply an offset to scroll
const offset = Offset(0, -10000);
// Use both methods: fling and drag
await tester.fling(
ageFinder,
offset,
1000,
warnIfMissed: false,
);
await tester.drag(
ageFinder,
offset,
warnIfMissed: false,
);
});
What errors did you receive? I've tried creating a minimal repro from the CupertinoPicker snippet you've provided and I did encounter some issues in testWidgets().
Some of the issues that I've noticed is that CupertinoPicker has "Age picker" as its key and GestureDetector has "Age Picker" key set. Note that the key is case-sensitive. Since you're going to test CupertinoPicker, the key set on GestureDetector seems to be unnecessary.
Aside from that, no widget was built for the test. I suggest going through the official docs for Flutter testing to get started https://flutter.dev/docs/cookbook/testing/widget/introduction
Here's the repro I've created from the snippets you've provided.
Code for testing widgets
void main(){
var ages = [18, 19, 20, 21, 22, 24, 24, 25];
testWidgets("CupertinoPicker test", (WidgetTester tester) async {
// build the app for the test
// https://flutter.dev/docs/cookbook/testing/widget/introduction#4-build-the-widget-using-the-widgettester
await tester.pumpWidget(MyApp());
// key should match the key set in the widget
await tester.tap(find.byKey(Key("Age Picker")));
await tester.drag(find.byKey(Key("Age Picker")), Offset(0.0, 70.0));
await tester.pumpAndSettle();
expect(ages[3], 21);
});
}
Sample code for the app
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
void main() {
runApp(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: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child:
testPicker(),
),
);
}
var _selectedAgeIndex = 0;
var scrollController = FixedExtentScrollController();
var dropDownPickerItemHeight = 50.0;
var ageValue;
var ages = [18, 19, 20, 21, 22, 24, 24, 25];
testPicker(){
return CupertinoPicker(
key: Key("Age Picker"),
scrollController: scrollController,
itemExtent: dropDownPickerItemHeight,
backgroundColor: Theme.of(context).canvasColor,
onSelectedItemChanged: (int index) {
setState(() {
_selectedAgeIndex = index;
ageValue = ages[index];
print('CupertinoPicker age[$index]: ${ages[index]}');
// if (ageValue == S.of(context).pickerDefaultValue) {
// ageDividerColor = Theme.of(context).errorColor;
// errorText = S.of(context).pickerErrorMessage;
// ageDividerWidth = 1.2;
// } else {
// ageDividerColor = Colors.black87;
// errorText = "";
// ageDividerWidth = 0.4;
// }
});
},
children: List<Widget>.generate(ages.length, (int index) {
return Center(
child: Text('${ages[index]}'),
);
}),
);
}
}

Textfield validation in Flutter

I am working on Flutter TextField widget. I want to show an error message below the TextField widget if the user does not fill that TextField. I only have to use TextField Widget not TextFormField in this case.
A Minimal Example of what you Want:
class MyHomePage extends StatefulWidget {
#override
MyHomePageState createState() {
return new MyHomePageState();
}
}
class MyHomePageState extends State<MyHomePage> {
final _text = TextEditingController();
bool _validate = false;
#override
void dispose() {
_text.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('TextField Demo'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('Error Showed if Field is Empty on Submit button Pressed'),
TextField(
controller: _text,
decoration: InputDecoration(
labelText: 'Enter the Value',
errorText: _validate ? 'Value Can\'t Be Empty' : null,
),
),
RaisedButton(
onPressed: () {
setState(() {
_text.text.isEmpty ? _validate = true : _validate = false;
});
},
child: Text('Submit'),
textColor: Colors.white,
color: Colors.blueAccent,
)
],
),
),
);
}
}
Flutter handles error text itself, so we don't require to use variable _validate. It will check at runtime whether you satisfied the condition or not.
final confirmPassword = TextFormField(
controller: widget.confirmPasswordController,
obscureText: true,
decoration: InputDecoration(
prefixIcon: Icon(Icons.lock_open, color: Colors.grey),
hintText: 'Confirm Password',
errorText: validatePassword(widget.confirmPasswordController.text),
contentPadding: EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 10.0),
),
);
String validatePassword(String value) {
if (!(value.length > 5) && value.isNotEmpty) {
return "Password should contain more than 5 characters";
}
return null;
}
Note: User must add at least one character to get this error message.
I would consider using a TextFormField with a validator.
Example:
class MyHomePageState extends State<MyHomePage> {
final _formKey = GlobalKey<FormState>();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('TextFormField validator'),
),
body: Form(
key: _formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextFormField(
decoration: InputDecoration(
hintText: 'Enter text',
),
textAlign: TextAlign.center,
validator: (text) {
if (text == null || text.isEmpty) {
return 'Text is empty';
}
return null;
},
),
RaisedButton(
onPressed: () {
if (_formKey.currentState.validate()) {
// TODO submit
}
},
child: Text('Submit'),
)
],
),
),
);
}
}
If you use TextFormField then you could easily implement 'Error
below your text fields'.
You can do this without using _validate or any other flags.
In this example, I have used validator method of TextFormField
widget. This makes the work a lot more easier and readable at the
same time.
I also used FormState to make the work more easier
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
final _form = GlobalKey<FormState>(); //for storing form state.
//saving form after validation
void _saveForm() {
final isValid = _form.currentState.validate();
if (!isValid) {
return;
}
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Form(
key: _form, //assigning key to form
child: ListView(
children: <Widget>[
TextFormField(
decoration: InputDecoration(labelText: 'Full Name'),
validator: (text) {
if (!(text.length > 5) && text.isNotEmpty) {
return "Enter valid name of more then 5 characters!";
}
return null;
},
),
TextFormField(
decoration: InputDecoration(labelText: 'Email'),
validator: (text) {
if (!(text.contains('#')) && text.isNotEmpty) {
return "Enter a valid email address!";
}
return null;
},
),
RaisedButton(
child: Text('Submit'),
onPressed: () => _saveForm(),
)
],
),
),
),
);
}
}
I hope this helps!
For TextFiled and TextFormFiled Validation you can use this Example I hope this will helpful for you people.
TextField(
enableInteractiveSelection: true,
autocorrect: false,
enableSuggestions: false,
toolbarOptions: ToolbarOptions(
copy: false,
paste: false,
cut: false,
selectAll: false,
),
controller: _currentPasswordController,
obscureText: passwordVisible,
decoration: InputDecoration(
errorText: Validators.password(
_currentPasswordController.text),
filled: true,
fillColor: Colors.white,
contentPadding:
const EdgeInsets.fromLTRB(20, 24, 12, 16),
border: const OutlineInputBorder(
borderRadius:
BorderRadius.all(Radius.circular(8.0))),
// filled: true,
labelText: 'Password',
hintText: 'Enter your password',
suffixIcon: GestureDetector(
onTap: () {
setState(() {
passwordVisible = !passwordVisible;
});
},
child: Container(
margin: const EdgeInsets.all(13),
child: Icon(
passwordVisible
? FontAwesomeIcons.eyeSlash
: Icons.remove_red_eye_sharp,
color: ColorUtils.primaryGrey,
size: 25)),
),
),
),
Validation Message Example Code
static password(String? txt) {
if (txt == null || txt.isEmpty) {
return "Invalid password!";
}
if (txt.length < 8) {
return "Password must has 8 characters";
}
if (!txt.contains(RegExp(r'[A-Z]'))) {
return "Password must has uppercase";
}
if (!txt.contains(RegExp(r'[0-9]'))) {
return "Password must has digits";
}
if (!txt.contains(RegExp(r'[a-z]'))) {
return "Password must has lowercase";
}
if (!txt.contains(RegExp(r'[#?!#$%^&*-]'))) {
return "Password must has special characters";
} else
return;
}

How do I make a TextSpan ripple when I tap it?

Imagine I have a long piece of text like this: HELLO THIS IS MY LONG SENTENCE . I want the word LONG to ripple (ink splash) when I tap it.
Let's say I have this code:
new RichText(
text: new TextSpan(
text: 'HELLO THIS IS MY ',
style: DefaultTextStyle.of(context).style,
children: <TextSpan>[
new TextSpan(text: 'LONG', style: new TextStyle(fontWeight: FontWeight.bold)),
new TextSpan(text: ' SENTENCE'),
],
),
)
Thanks!
If you want a generic solution to place widgets over portions of text, see this gist.
You can use the following code to have the ripple constrained to a specific section of the text:
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'dart:ui' show TextBox;
import 'dart:math';
void main() {
runApp(new MaterialApp(
home: new Material(
child: new Center(
child: new Demo(),
),
),
));
}
class Demo extends StatelessWidget {
final TextSelection textSelection =
const TextSelection(baseOffset: 17, extentOffset: 21);
final GlobalKey _textKey = new GlobalKey();
#override
Widget build(context) => new Stack(
children: <Widget>[
new RichText(
key: _textKey,
text: new TextSpan(
text: 'HELLO THIS IS MY ',
style: DefaultTextStyle.of(context).style,
children: <TextSpan>[
new TextSpan(
text: 'LONG',
style: new TextStyle(fontWeight: FontWeight.bold)),
new TextSpan(text: ' SENTENCE'),
],
),
),
new Positioned.fill(
child: new LayoutBuilder(
builder: (context, _) => new Stack(
children: <Widget>[
new Positioned.fromRect(
rect: _getSelectionRect(),
child: new InkWell(
onTap: () => {}, // needed to show the ripple
),
),
],
),
),
),
],
);
Rect _getSelectionRect() =>
(_textKey.currentContext.findRenderObject() as RenderParagraph)
.getBoxesForSelection(textSelection)
.fold(
null,
(Rect previous, TextBox textBox) => new Rect.fromLTRB(
min(previous?.left ?? textBox.left, textBox.left),
min(previous?.top ?? textBox.top, textBox.top),
max(previous?.right ?? textBox.right, textBox.right),
max(previous?.bottom ?? textBox.bottom, textBox.bottom),
),
) ??
Rect.zero;
}
You can accomplish this effect by adapting the code in ink_well.dart.
In this example I configured the rectCallback to expand to the containing card, but you could provide a smaller rectangle with the splash be centered around the point of the tap.
import 'package:flutter/material.dart';
import 'package:flutter/gestures.dart';
import 'dart:collection';
void main() {
runApp(new MaterialApp(home: new DemoApp()));
}
class DemoText extends StatefulWidget {
#override
DemoTextState createState() => new DemoTextState();
}
class DemoTextState<T extends InkResponse> extends State<T>
with AutomaticKeepAliveClientMixin {
Set<InkSplash> _splashes;
InkSplash _currentSplash;
#override
bool get wantKeepAlive => (_splashes != null && _splashes.isNotEmpty);
void _handleTapDown(TapDownDetails details) {
final RenderBox referenceBox = context.findRenderObject();
InkSplash splash;
splash = new InkSplash(
controller: Material.of(context),
referenceBox: referenceBox,
containedInkWell: true,
rectCallback: () => referenceBox.paintBounds,
position: referenceBox.globalToLocal(details.globalPosition),
color: Theme.of(context).splashColor,
onRemoved: () {
if (_splashes != null) {
assert(_splashes.contains(splash));
_splashes.remove(splash);
if (_currentSplash == splash) _currentSplash = null;
updateKeepAlive();
} // else we're probably in deactivate()
});
_splashes ??= new HashSet<InkSplash>();
_splashes.add(splash);
_currentSplash = splash;
updateKeepAlive();
}
void _handleTap(BuildContext context) {
_currentSplash?.confirm();
_currentSplash = null;
Feedback.forTap(context);
}
void _handleTapCancel() {
_currentSplash?.cancel();
_currentSplash = null;
}
#override
void deactivate() {
if (_splashes != null) {
final Set<InkSplash> splashes = _splashes;
_splashes = null;
for (InkSplash splash in splashes) splash.dispose();
_currentSplash = null;
}
assert(_currentSplash == null);
super.deactivate();
}
Widget build(BuildContext context) {
return new Padding(
padding: new EdgeInsets.all(20.0),
child: new RichText(
text: new TextSpan(
text: 'HELLO THIS IS MY ',
style: DefaultTextStyle.of(context).style,
children: <TextSpan>[
new TextSpan(
recognizer: new TapGestureRecognizer()
..onTapCancel = _handleTapCancel
..onTapDown = _handleTapDown
..onTap = () => _handleTap(context),
text: 'LONG',
style: new TextStyle(fontWeight: FontWeight.bold),
),
new TextSpan(text: ' SENTENCE'),
],
),
),
);
}
}
class DemoApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Scaffold(
body: new Center(
child: new Container(
height: 150.0,
width: 150.0,
child: new Card(
child: new DemoText(),
),
),
),
);
}
}
By 2019, we can use this:
RichText(
textAlign: TextAlign.center,
text: TextSpan(
style: textTheme.bodyText2,
text: "HELLO THIS IS MY",
children: [
WidgetSpan(
child: InkWell(
onTap: () {},
child: Text(
"SENTENCE",
style: TextStyle(color: colorScheme.primary),
),
),
),
],
),
),

Resources