Month and Year custom picker Flutter (without package)
So, I was working on a project (with flutter) which needs a date picker. But this time this picker only have to pick months and year not the date.
What I tried:
- I tried with the default flutter DatePicker. Here I used DatePickerMode = DatePickerMode.year, which only allow me to choose year. Not month.
- I tried YearPicker hoping to get some luck. But this time I found out it works the same as point no 1.
- On my first 2 attempt I did not tried any package from puv.dev, cause I didn’t want to use any package for only one task. But after failing these 2 attempt I tried some of the packages from their. But again these packages needs FlutterLocalization. But in my app I don’t really need to use localization.
So, I decided to build my own picker and share it to all so that others don’t have to face the same problem as I have.
It was very simple to be honest. Here we go,
Firstly declare a list of strings only containing all the months serially. Like this.
final List<String> monthList = [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December"
];
Then I created a StatefulWidget
named MonthPicker and took everything I need from constructor like this.
class MonthPicker extends StatefulWidget {
MonthPicker({required this.initialYear, required this.startYear, required this.endYear, this.currentYear, required this.month, Key? key}) : super(key: key);
late int initialYear;
late int startYear;
late int endYear;
late int month;
// yror code here
}
You can take these variable as you wish. Here initialYear is the year which will be shown initially, month is the number of month will be shown initially and you can guess from the name of startYear and endyear what these variable does.
So, now I have list of months (that wont change no matter where you are or you can take your own local months list) and I have starting year and end year. Now all we need is creating a new list of year. How will we do it?
Well, just keep on adding 1 from starting year till end year and adding them to a list. List this you can add them in initState function.
// your code
List<String> _yearList = [];
@override
void initState() {
for(int i = widget.startYear; i <= widget.endYear; i++){
_yearList.add(i.toString());
}
// your code
super.initState();
}
// your code
Now we have a months list and a year list. So the rest is easy. Now it is up to you how do you want to show it and how do you want to let your user to select year or months from the list.
You may use a simple Dropdown or you may use ListView.Builder, ExpandedTile or even in GridView.Builder.
I personally used dropdown. I have shown all these on a dialog box. I am giving my code here for better understanding. I will also show you how to do it with ListView.Builder.
import 'package:flutter/material.dart';
class MonthPicker extends StatefulWidget {
MonthPicker({required this.initialYear, required this.startYear, required this.endYear, this.currentYear, required this.month, Key? key}) : super(key: key);
late int initialYear;
late int startYear;
late int endYear;
late int? currentYear;
late int month;
@override
State<MonthPicker> createState() => _MonthPickerState();
}
class _MonthPickerState extends State<MonthPicker> {
final List<String> _monthList = [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December"
];
List<String> _yearList = [];
late int selectedMonthIndex;
late int selectedYearIndex;
String selectedMonth = "";
String selectedYear = "";
@override
void initState() {
for(int i = widget.startYear; i <= widget.endYear; i++){
_yearList.add(i.toString());
}
selectedMonthIndex = widget.month - 1;
selectedYearIndex = _yearList.indexOf(widget.currentYear?.toString()??widget.initialYear.toString());
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
setState(() {
selectedMonth = _monthList[selectedMonthIndex];
selectedYear = _yearList[selectedYearIndex];
});
});
super.initState();
}
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 30, vertical: 20),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Align(
alignment: Alignment.centerLeft,
child: Text(
"Pick a date",
style: TextStyle(
color: Colors.grey,
fontWeight: FontWeight.bold,
fontSize: 15
),
)
),
Divider(),
SizedBox(height: 20,),
Row(
mainAxisSize: MainAxisSize.max,
children: [
Expanded(
child: DropdownButton<String>(
underline: Container(),
items: _monthList.map((e){
return DropdownMenuItem<String>(
value: e,
child: Text(e)
);
}).toList(),
value: selectedMonth,
onChanged: (val){
setState(() {
selectedMonthIndex = _monthList.indexOf(val!);
selectedMonth = val??"";
});
},
),
),
const SizedBox(width: 20),
Expanded(
child: DropdownButton<String>(
underline: Container(),
items: _yearList.map((e){
return DropdownMenuItem<String>(
value: e,
child: Text(e)
);
}).toList(),
value: selectedYear,
onChanged: (val){
setState(() {
selectedYear = val??"";
});
},
),
)
],
),
SizedBox(height: 40,),
Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
style: ElevatedButton.styleFrom(primary: Colors.red),
onPressed: (){
Navigator.pop(context);
},
child: Text("Cancel")
),
ElevatedButton(
style: ElevatedButton.styleFrom(primary: Colors.green),
onPressed: (){
// save your value
Navigator.pop(context,);
},
child: Text("OK",)
)
],
)
],
),
);
}
}
// this how I have called the above widget
// ... your code ...
DateTime currentDate = DateTime.now();
showDialog(
context: context,
barrierDismissible: dismissible,
builder: (BuildContext context) {
return BackdropFilter(
filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0),
child: WillPopScope(
onWillPop: () async {
return dismissible;
},
child: Dialog(
backgroundColor: bgColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15.sp),
),
child: MonthPicker(initialYear: currentDate.year, startYear: 2000, endYear: currentDate.year, month: currentDate.month),
),
),
);
},
);
// ... your code ...
So after adding some extra finishing touch on UI level the result will be like this.
I have used setState for general understanding but you can use your own state management.
Now let’s move into how can you use ListView.Builder.
Well you may use listview very easily by letting the user select any year or month by clicking on the screen. It is very easy right? you can also use this same tactics, if you want to use ExpandedTile or GridView.Builder. But let’s dive a bit further with listview.
I know lot of you including myself wonder how can we make this,
As you can see here user can not select anything, whether he/she can scroll any month or year to the view port and which will count as a selected month or year.
Well first thing you need to understand what is a viewport. I am not going to elaborate that here. But there is no straight forward way to do this. I have done this with ViewportDetector. for that you need to install it from pub.dev.
int _currentItem = 0;
// ... your code ...
Center(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 50),
child: SizedBox(
height: 60,
child: ListView.builder(
itemCount: _monthList.length,
itemBuilder: (context, index){
return SizedBox(
height: 30,
child: VisibilityDetector(
key: Key(index.toString()),
onVisibilityChanged: (VisibilityInfo info) {
if (info.visibleFraction == 1)
setState(() {
_currentItem = index;
});
},
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 5),
child: Container(
color: Colors.grey[100],
child: Text(_monthList[index],
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 15),
)
),
),
)
);
}
),
),
),
),
// ... your code ...
So this is only the month selection part. You can also do the same for year.
So after executing the above code, it will look like this,
I hope this will help you. If there anything you need to add or to suggest please do let me know. Thank you.
Happy coding :)