Calendar weekview in flutter - calendar

I am trying to program a weekview like in the google calendar for flutter.
I want to display a timetable where the events begin and end at individual times (e.g. 9:34, 17:11) and at exactly the times they should be displayed in the calendar. So not rounded up or down to the full hour as I currently implemented it with a table widget.
What would be the best way to achieve such an view. I don't know where to start. The horizontal scrolling itself isn't so important. But the vertical one is my problem. How do I place these event widgets at a specific position in a scrollable list?
I appreciate every kind of answer. It would help me very much.

Here is a poor mans version, using a CustomScrollView and simple Stacks with Positioned children.
Things are getting more difficult if you also want horizontal scrolling. For really complex layouts with animations, you may need a custom layout.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Playground',
home: TestPage(),
);
}
}
class TestPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Calendar'),
),
body: WeekView(),
);
}
}
const headerHeight = 50.0;
const hourHeight = 100.0;
class WeekView extends StatelessWidget {
#override
Widget build(BuildContext context) {
return CustomScrollView(
slivers: <Widget>[
SliverPersistentHeader(
delegate: WeekViewHeaderDelegate(),
pinned: true,
),
SliverToBoxAdapter(
child: _buildGrid(),
)
],
);
}
Widget _buildGrid() {
return SizedBox(
height: hourHeight * 24,
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: List.generate(7, (d) => _buildColumn(d)),
),
);
}
Widget _buildColumn(int d) {
return Expanded(
child: Stack(
children: <Widget>[
Positioned(
left: 0.0,
top: d * 25.0,
right: 0.0,
height: 50.0 * (d + 1),
child: Container(
margin: EdgeInsets.symmetric(horizontal: 2.0),
color: Colors.orange[100 + d * 100],
),
)
],
),
);
}
}
class WeekViewHeaderDelegate extends SliverPersistentHeaderDelegate {
#override
Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
return Container(
color: Colors.red.withOpacity(0.5),
child: Center(
child: Text('HEADER'),
),
);
}
#override
double get maxExtent => headerHeight;
#override
double get minExtent => headerHeight;
#override
bool shouldRebuild(SliverPersistentHeaderDelegate oldDelegate) {
return false;
}
}

I'm a bit late here but I've created a library that can offer you exactly what you want :
It's called FlutterWeekView and here are some links if you're still interested :
pub.dev
Github
Preview :

Related

The following NoSuchMethodError was thrown building LoadDataFromFireStore. The getter 'keys' was called on null

I want to connect to my firebase database as now called "realtime database".
My Apps is running smoothly, but I am getting following error if I want to start the method in my app through a button:
======== Exception caught by widgets library =======================================================
The following NoSuchMethodError was thrown building LoadDataFromFireStore(dirty, state: LoadDataFromFireStoreState#20451):
The getter 'keys' was called on null.
Receiver: null
Tried calling: keys
The relevant error-causing widget was:
LoadDataFromFireStore file:///C:/Users/Nutzer/AndroidStudioProjects/tennis_sv_schwaig/lib/widget/kalender.dart:17:13
When the exception was thrown, this was the stack:
#0 Object.noSuchMethod (dart:core-patch/object_patch.dart:51:5)
#1 LoadDataFromFireStoreState._showCalendar (package:tennis_sv_schwaig/widget/kalender.dart:58:34)
#2 LoadDataFromFireStoreState.build (package:tennis_sv_schwaig/widget/kalender.dart:49:13)
#3 StatefulElement.build (package:flutter/src/widgets/framework.dart:4744:28)
#4 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:4627:15)
...
====================================================================================================
I do not know if theres is a collision with a keyForm in an other method before. Could this be possible?
Here is the code from the method:
import 'dart:math';
import 'dart:collection';
import 'package:flutter/material.dart';
import 'package:syncfusion_flutter_calendar/calendar.dart';
import 'package:intl/intl.dart';
import 'package:firebase_database/firebase_database.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
void main() => runApp(kalender());
class kalender extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: LoadDataFromFireStore(),
);
}
}
class LoadDataFromFireStore extends StatefulWidget {
#override
LoadDataFromFireStoreState createState() => LoadDataFromFireStoreState();
}
class LoadDataFromFireStoreState extends State<LoadDataFromFireStore> {
DataSnapshot querySnapshot;
dynamic data;
List<Color> _colorCollection;
#override
void initState() {
_initializeEventColor();
getDataFromDatabase().then((results) {
setState(() {
if (results != null) {
querySnapshot = results;
}
});
});
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: _showCalendar(),
);
}
_showCalendar() {
if (querySnapshot != null) {
List<Meeting> collection;
var showData = querySnapshot.value;
Map<dynamic, dynamic> values = showData;
List<dynamic> key = values.keys.toList();
if (values != null) {
for (int i = 0; i < key.length; i++) {
data = values[key[i]];
collection ??= <Meeting>[];
final Random random = new Random();
collection.add(Meeting(
eventName: data['Subject'],
isAllDay: false,
from: DateFormat('dd/MM/yyyy HH:mm:ss').parse(data['StartTime']),
to: DateFormat('dd/MM/yyyy HH:mm:ss').parse(data['EndTime']),
background: _colorCollection[random.nextInt(9)]));
}
} else {
return Center(
child: CircularProgressIndicator(),
);
}
return SafeArea(
child: Column(
children: [
Container(
height: 400,
child: SfCalendar(
view: CalendarView.month,
initialDisplayDate: DateTime(2020, 4, 5, 9, 0, 0),
dataSource: _getCalendarDataSource(collection),
monthViewSettings: MonthViewSettings(showAgenda: true),
),
),
RaisedButton(onPressed: () {
final dbRef = FirebaseDatabase.instance.reference().child("CalendarData");
dbRef.push().set({
"StartTime": '07/04/2020 07:00:00',
"EndTime": '07/04/2020 08:00:00',
"Subject":'NewMeeting',
}).then((_) {
Scaffold.of(context).showSnackBar(
SnackBar(content: Text('Successfully Added')));
}).catchError((onError) {
print(onError);
});
}, child: Text("Add")),
RaisedButton(onPressed: () {
final dbRef = FirebaseDatabase.instance.reference().child("CalendarData");
dbRef.remove();
}, child: Text("Delete")),
],
));
}
}
void _initializeEventColor() {
this._colorCollection = new List<Color>();
_colorCollection.add(const Color(0xFF0F8644));
_colorCollection.add(const Color(0xFF8B1FA9));
_colorCollection.add(const Color(0xFFD20100));
_colorCollection.add(const Color(0xFFFC571D));
_colorCollection.add(const Color(0xFF36B37B));
_colorCollection.add(const Color(0xFF01A1EF));
_colorCollection.add(const Color(0xFF3D4FB5));
_colorCollection.add(const Color(0xFFE47C73));
_colorCollection.add(const Color(0xFF636363));
_colorCollection.add(const Color(0xFF0A8043));
}
}
MeetingDataSource _getCalendarDataSource([List<Meeting> collection]) {
List<Meeting> meetings = collection ?? <Meeting>[];
return MeetingDataSource(meetings);
}
class MeetingDataSource extends CalendarDataSource {
MeetingDataSource(List<Meeting> source) {
appointments = source;
}
#override
DateTime getStartTime(int index) {
return appointments[index].from;
}
#override
DateTime getEndTime(int index) {
return appointments[index].to;
}
#override
bool isAllDay(int index) {
return appointments[index].isAllDay;
}
#override
String getSubject(int index) {
return appointments[index].eventName;
}
#override
Color getColor(int index) {
return appointments[index].background;
}
}
getDataFromDatabase() async {
var value = FirebaseDatabase.instance.reference();
var getValue = await value.child('CalendarData').once();
return getValue;
}
class Meeting {
Meeting({this.eventName, this.from, this.to, this.background, this.isAllDay});
String eventName;
DateTime from;
DateTime to;
Color background;
bool isAllDay;
}
Hope its enough and anybody could help. Thanks a lot!

Flutter call function for each item in list

I've got a 2x2 grid that I am trying to populate with cards, where each card is of a specific style. Each card has a title and a route which when clicked opens another page (route). I'd like to specify the "name" of the card and the "name of the page" which the the click should lead to for each "name" in an index of "names".
The problem is 1. I'm not sure how to do the for-each loop within the widget. 2. Not sure how to make this work for more than 1 parameter being; name of card and name of new page.
I have already tried a few options, 2 of which are shown below.
class SubjectsPage extends StatefulWidget {
#override
_SubjectsPageState createState() => new _SubjectsPageState();
}
class _SubjectsPageState extends State<SubjectsPage> with TickerProviderStateMixin {
#override
Widget build(BuildContext context) {
var names = ["one", "two", "three", "four"];
return new Material(
child: new Container(
child: new Center(
child: GridView.count(
crossAxisCount: 2,
padding: EdgeInsets.fromLTRB(16.0, 128.0, 16.0, 16.0),
childAspectRatio: 8.0 / 8.5,
children: <Widget>[
//option 1
names.forEach((unit) => Cards(unit: unit,)),
//option 2
for (var i = 0; i < names.length; i++) {
Cards(unit: names[i])
}
],
),
),
),
);
}
}
The error for option 1 is that "the expression here has a type of 'void', and therefore cannot be used."
The error for option 2 is that "the element type 'Set' can't be assigned to the list type 'Widget'."
Yes, you cannot use forEach but map. You can map strings to widget using map method.
Example:
class SubjectsPage extends StatefulWidget {
#override
_SubjectsPageState createState() => new _SubjectsPageState();
}
class _SubjectsPageState extends State<SubjectsPage>
with TickerProviderStateMixin {
#override
Widget build(BuildContext context) {
var names = ["one", "two", "three", "four"];
return Material(
child: Container(
child: Center(
child: GridView.count(
crossAxisCount: 2,
padding: EdgeInsets.fromLTRB(16.0, 128.0, 16.0, 16.0),
childAspectRatio: 8.0 / 8.5,
children: names.map((String name) {
return Cards(unit: name);
}).toList(),
),
),
),
);
}
}
Or: Your option two can be done like
class SubjectsPage extends StatefulWidget {
#override
_SubjectsPageState createState() => new _SubjectsPageState();
}
class _SubjectsPageState extends State<SubjectsPage>
with TickerProviderStateMixin {
#override
Widget build(BuildContext context) {
var names = ["one", "two", "three", "four"];
return Material(
child: Container(
child: Center(
child: GridView.count(
crossAxisCount: 2,
padding: EdgeInsets.fromLTRB(16.0, 128.0, 16.0, 16.0),
childAspectRatio: 8.0 / 8.5,
children: [
for(String name in names) Cards(unit:name)
],
),
),
),
);
}
}
Hope that helps!

SetState() called in constructor

I've build a Custemized List. Now I include a Checkbox and if I would checked or unchecked , the following error was thrown: 'setState() called in constructor'
class Lists extends StatefulWidget{
#override
_List createState() => _List();
}
class _List extends State<Lists> {
bool checkedvalue = true;
#override
Widget build(BuildContext context) {
return futureBuilder();
}
Widget futureBuilder(){
var futureBuilder = new FutureBuilder(
future: rest.fetchPost(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
case ConnectionState.waiting:
return new Text('loading...');
default:
if (snapshot.hasError)
return new Text('Error: ${snapshot.error}');
else
return listBuilder(context, snapshot);
}
}
);
return new Scaffold(
body: futureBuilder,
);
}
Widget listBuilder(BuildContext context, AsyncSnapshot snapshot) {
List<rest.Status> values = snapshot.data;
if (values == null || values.length == 0){
return null;
}
int items = values.length;
return ListView.builder(
itemCount: items,
itemBuilder: (BuildContext context, int index) {
String statusText;
Image image ;
Uint8List bytes;
if(statusList.globalStatus != null){
for(int i=0;i< statusList.globalStatus.length; i++){
if(values[index].statusID == statusList.globalStatus[i].id){
if(statusList.globalStatus[i].kurzform != null){
statusText = statusList.globalStatus[i].kurzform;
}else{
statusText = statusList.globalStatus[i].kurzform;
}
if (statusList.globalStatus[i].icon != null){
bytes = base64Decode(statusList.globalStatus[i].icon);
image = new Image.memory(bytes) ;
}
}
if(image== null || statusText == null){
statusText= 'Kein Status';
image= new Image.asset('assets/null.png');
}
}
}
return new Container(
decoration: new BoxDecoration(
border: Border(top: BorderSide(
color: Colors.black26,
width: 1
)
)
),
child:Column(
children: <Widget>[
CustomListItemTwo(
statusText: statusText,
status:image,
materialNR: values[index].uArtText,
material: values[index].untersuchungsMaterialName,
probenArt: values[index].probenart,
eingansdatum: values[index].eingangsdatumText,
patient: values[index].vorname + ' ' + values[index].nachname ,
geburtsdatum: values[index].geburtstagText ,
),
Checkbox(
value: checkedvalue ,
onChanged: (bool newValue) =>
setState(() {
checkedvalue = newValue;
})
),
]
),
);
}
);
}
}
I/flutter ( 5067): ══╡ EXCEPTION CAUGHT BY GESTURE ╞═══════════════════════════════════════════════════════════════════
I/flutter ( 5067): The following assertion was thrown while handling a gesture:
I/flutter ( 5067): setState() called in constructor: _List#9044e(lifecycle state: created, no widget, not mounted)
I/flutter ( 5067): This happens when you call setState() on a State object for a widget that hasn't been inserted into
I/flutter ( 5067): the widget tree yet. It is not necessary to call setState() in the constructor, since the state is
I/flutter ( 5067): already assumed to be dirty when it is initially created.
My code below is not testet.
There is somewhat a concept error in your code. You should NOT fetch anything inside your build method!
If you put a print e.g. "building..." in your build-method (as I did below) you will see why. The build method is called more than you might think. So you are calling a WebService or whatever more then once, the response will come more then once. Actually the setState() method will trigger a build.
If you want to pull something at the beginning use the initState() method. This method will be called once when the state was created. Use Variables for the state of the call and react to it in the build Method (as said before setState() will trigger a rebuild).
I refactored your code a bit, having this concept in mind, your switch/checkbox problem probably will be gone.
Also please take a look how to use Futures https://api.flutter.dev/flutter/dart-async/Future-class.html
class Lists extends StatefulWidget {
#override
_List createState() => _List();
}
class _List extends State<Lists> {
bool checkedvalue = true;
bool loading = true;
AsyncSnapshot asyncSnapshot = null;
#override
void initState() {
futureBuilder();
super.initState();
}
#override
Widget build(BuildContext context) {
print("building...");
if(asyncSnapshot != null && asyncSnapshot.hasError){
return Text("Error : ${asyncSnapshot.error}");
}
return (loading) ? Text("LOADING") : listBuilder(context, asyncSnapshot);
}
void futureBuilder() async {
rest.fetchPost().then((snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
case ConnectionState.waiting:
setState(() {
loading = true;
});
break;
default:
if (snapshot.hasError) {
setState(() {
loading = false;
});
} else {
setState(() {
loading = false;
asyncSnapshot = snapshot;
});
}
}
});
}
.....

Can someone help me with a framework or something else to implement this type of calendar widget in flutter

I would like to show a calendar not as a dialog. And I'll like the use to be able to select date interval as in the screenshot.
I would recommend you not to reinvent the wheel and pick one of the community calendar widgets (like that one), but in case you need a custom solution, you may start with something really simple. For example, if you need to pick a range you may just take a grid and a few buttons like that:
import 'package:flutter/material.dart';
class CalendarPage extends StatefulWidget {
final String title;
CalendarPage({Key key, this.title}) : super(key: key);
#override
State<StatefulWidget> createState() => _CalendarPageState();
}
class _CalendarPageState extends State<CalendarPage> {
int _left = -1;
int _right = -1;
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: GridView.count(
crossAxisCount: 7,
children: List.generate(31, (index) {
return Container(
decoration: BoxDecoration(
border: Border.all(width: 2.0, color: Colors.black38),
color: _isInBounds(index)
? Colors.yellow[100]
: Colors.transparent,
borderRadius: const BorderRadius.all(const Radius.circular(8.0)),
),
margin: const EdgeInsets.all(2.0),
child: FlatButton(
onPressed: () => _handleTap(index),
child: Text('${index + 1}',
style: Theme.of(context).textTheme.body2,
textAlign: TextAlign.center)));
}),
));
}
void _handleTap(index) {
setState(() {
if (_left == -1)
_left = index;
else if (_right == -1) _right = index;
});
}
bool _isInBounds(int index) => _left <= index && index <= _right;
}
UI: https://flutter.io/tutorials/layout/
Selecting a range: https://www.didierboelens.com/2018/07/range-slider/
You'll learn a lot from these. Good luck!

Flutter, adjust widget in list depending on scrolloffset

How would I set the height of a Widget in a ListView depending on the offset of the underlying scrollview?
Thanks for any hints :)
To know the current scroll offset of ListView, you can just pass a controller and check the offset of controller.
example:
class ScrollOffsetWidget extends StatefulWidget {
#override
_ScrollOffsetWidgetState createState() => new _ScrollOffsetWidgetState();
}
class _ScrollOffsetWidgetState extends State<ScrollOffsetWidget> {
List<double> height = <double>[200.0,200.0,200.0,200.0,200.0];
double scrollOffset = 0.0;
static ScrollController _controller;
_ScrollOffsetWidgetState(){
_controller = new ScrollController(initialScrollOffset: scrollOffset);
_controller.addListener(listen);
}
void listen(){
final List<double> newHeight = height;
if(_controller.offset>100.0){
newHeight[1] = 400.0;
}else{
newHeight[1] = 200.0;
}
setState((){
height = newHeight;
});
}
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
body: new ListView(
controller: _controller,
children: <Widget>[
new AnimatedContainer(duration: const Duration(milliseconds: 300),height: height[0],color: Colors.primaries[0],),
new AnimatedContainer(duration: const Duration(milliseconds: 300),height: height[1],color: Colors.primaries[1],),
new AnimatedContainer(duration: const Duration(milliseconds: 300),height: height[2],color: Colors.primaries[2],),
new AnimatedContainer(duration: const Duration(milliseconds: 300),height: height[3],color: Colors.primaries[3],),
new AnimatedContainer(duration: const Duration(milliseconds: 300),height: height[4],color: Colors.primaries[4],)
],
),
),
);
}
}
Hope that helped!

Resources