Flutter: TableCalendar how to display events from Firebase FireStore - flutter

I'm making app with Calendar. As calendar i'm using Table Calendar. I also use FireStore in my app. After implementing TableCalendar i followed github demo to display events Now i want to use FireStore with Table Calendar. How can i make Table Calendar display events from FireStore? I have node called 'date' in Firestore witch contains timestamp.
EventPage code:
late final ValueNotifier<List<Event>> _selectedEvents;
final Stream<QuerySnapshot> eventsStream =
FirebaseFirestore.instance.collection('events').snapshots();
DateTime _focusedDay = DateTime.now();
DateTime? _selectedDay;
#override
void initState() {
super.initState();
_selectedDay = _focusedDay;
_selectedEvents = ValueNotifier(_getEventsForDay(_selectedDay!));
}
#override
void dispose() {
super.dispose();
_selectedEvents.dispose();
}
List<Event> _getEventsForDay(DateTime day) {
return kEvents[day] ?? [];
}
void _onDaySelected(DateTime selectedDay, DateTime focusedDay) {
if (!isSameDay(_selectedDay, selectedDay)) {
setState(() {
_selectedDay = selectedDay;
_focusedDay = focusedDay;
});
_selectedEvents.value = _getEventsForDay(selectedDay);
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: StreamBuilder<Object>(
stream: eventsStream,
builder: (context, snapshot) {
if (snapshot.hasData) {}
return Column(
children: [
Card(
color: MainTheme.lightTheme.primaryColor,
child: TableCalendar<Event>(
firstDay: kFirstDay,
lastDay: kLastDay,
focusedDay: _focusedDay,
selectedDayPredicate: (day) => isSameDay(_selectedDay, day),
eventLoader: _getEventsForDay,
headerStyle: HeaderStyle(
titleTextStyle: TextStyle(color: Colors.white),
formatButtonVisible: false,
leftChevronIcon: Icon(
Icons.chevron_left,
color: Colors.white,
),
rightChevronIcon: Icon(
Icons.chevron_right,
color: Colors.white,
),
),
daysOfWeekStyle: DaysOfWeekStyle(
weekdayStyle: TextStyle(color: Colors.white),
weekendStyle: TextStyle(color: Colors.white),
),
startingDayOfWeek: StartingDayOfWeek.monday,
onDaySelected: _onDaySelected,
),
),
SizedBox(height: 8),
Expanded(
child: ValueListenableBuilder<List<Event>>(
valueListenable: _selectedEvents,
builder: (context, value, _) {
return ListView.builder(
itemCount: value.length,
itemBuilder: (context, index) {
return ListTile(
title: Text('${value[index]}'),
);
},
);
},
),
)
],
);
},
),
),
);
}
EventsModel code
class Event {
final String title;
const Event(this.title);
#override
String toString() => title;
}
final kEvents = LinkedHashMap<DateTime, List<Event>>(
equals: isSameDay,
hashCode: getHashCode,
)..addAll(_kEventSource);
final _kEventSource = Map.fromIterable(
List.generate(50, (index) => index),
key: (item) => DateTime.utc(kFirstDay.year, kFirstDay.month, item * 5),
value: (item) => List.generate(
item % 4 + 1,
(index) => Event('Event $item | ${index + 1}'),
),
)..addAll(
{
kToday: [
Event('Today\'s Event 1'),
Event('Today\'s Event 2'),
],
},
);
int getHashCode(DateTime key) {
return key.day * 1000000 + key.month * 10000 + key.year;
}
final kToday = DateTime.now();
final kFirstDay = DateTime(kToday.year, kToday.month - 6, kToday.day);
final kLastDay = DateTime(kToday.year, kToday.month + 6, kToday.day);
P.s i created new Function witch gets data from FireStore and stores it in List. How can i use this list to display events in Table Calendar?
getEventsList() async {
var data = await FirebaseFirestore.instance.collection('events').get();
setState(() {
// eventsList = data.docs;
});
for (int i = 0; i < data.docs.length; i++)
eventsList.add(data.docs[i]['date']);
print(eventsList);
return 'compelte';
}

Related

How to use table calendar in flutter to create events

I have copied this code from table calender repo on github table calender and i am getting this errors: Undefined name 'kEvents',kFirstDay, kLastDay and the method 'daysInRange' isn't defined for the type '_TableEventsExampleState'. How do i solve it. Here is my code, you can also check out the code on the link.
import 'package:flutter/material.dart';
import 'package:table_calendar/table_calendar.dart';
import 'package:event/event.dart';
import '../utils.dart';
class TableEventsExample extends StatefulWidget {
#override
_TableEventsExampleState createState() => _TableEventsExampleState();
}
class _TableEventsExampleState extends State<TableEventsExample> {
late final ValueNotifier<List<Event>> _selectedEvents;
CalendarFormat _calendarFormat = CalendarFormat.month;
RangeSelectionMode _rangeSelectionMode = RangeSelectionMode
.toggledOff; // Can be toggled on/off by longpressing a date
DateTime _focusedDay = DateTime.now();
DateTime? _selectedDay;
DateTime? _rangeStart;
DateTime? _rangeEnd;
#override
void initState() {
super.initState();
_selectedDay = _focusedDay;
_selectedEvents = ValueNotifier(_getEventsForDay(_selectedDay!));
}
#override
void dispose() {
_selectedEvents.dispose();
super.dispose();
}
List<Event> _getEventsForDay(DateTime day) {
// Implementation example
return kEvents[day] ?? [];
}
List<Event> _getEventsForRange(DateTime start, DateTime end) {
// Implementation example
final days = daysInRange(start, end);
return [
for (final d in days) ..._getEventsForDay(d),
];
}
void _onDaySelected(DateTime selectedDay, DateTime focusedDay) {
if (!isSameDay(_selectedDay, selectedDay)) {
setState(() {
_selectedDay = selectedDay;
_focusedDay = focusedDay;
_rangeStart = null; // Important to clean those
_rangeEnd = null;
_rangeSelectionMode = RangeSelectionMode.toggledOff;
});
_selectedEvents.value = _getEventsForDay(selectedDay);
}
}
void _onRangeSelected(DateTime? start, DateTime? end, DateTime focusedDay) {
setState(() {
_selectedDay = null;
_focusedDay = focusedDay;
_rangeStart = start;
_rangeEnd = end;
_rangeSelectionMode = RangeSelectionMode.toggledOn;
});
// `start` or `end` could be null
if (start != null && end != null) {
_selectedEvents.value = _getEventsForRange(start, end);
} else if (start != null) {
_selectedEvents.value = _getEventsForDay(start);
} else if (end != null) {
_selectedEvents.value = _getEventsForDay(end);
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('TableCalendar - Events'),
),
body: Column(
children: [
TableCalendar<Event>(
firstDay: kFirstDay,
lastDay: kLastDay,
focusedDay: _focusedDay,
selectedDayPredicate: (day) => isSameDay(_selectedDay, day),
rangeStartDay: _rangeStart,
rangeEndDay: _rangeEnd,
calendarFormat: _calendarFormat,
rangeSelectionMode: _rangeSelectionMode,
eventLoader: _getEventsForDay,
startingDayOfWeek: StartingDayOfWeek.monday,
calendarStyle: CalendarStyle(
// Use `CalendarStyle` to customize the UI
outsideDaysVisible: false,
),
onDaySelected: _onDaySelected,
onRangeSelected: _onRangeSelected,
onFormatChanged: (format) {
if (_calendarFormat != format) {
setState(() {
_calendarFormat = format;
});
}
},
onPageChanged: (focusedDay) {
_focusedDay = focusedDay;
},
),
const SizedBox(height: 8.0),
Expanded(
child: ValueListenableBuilder<List<Event>>(
valueListenable: _selectedEvents,
builder: (context, value, _) {
return ListView.builder(
itemCount: value.length,
itemBuilder: (context, index) {
return Container(
margin: const EdgeInsets.symmetric(
horizontal: 12.0,
vertical: 4.0,
),
decoration: BoxDecoration(
border: Border.all(),
borderRadius: BorderRadius.circular(12.0),
),
child: ListTile(
onTap: () => print('${value[index]}'),
title: Text('${value[index]}'),
),
);
},
);
},
),
),
],
),
);
}
}
With the code that you have provided, you don't seem to have defined any of the variables you mention or the method. You try to access the variables down in the widget creation but flutter doesn't know where they point to. You need to first define the variables and the method and then use them.

How to get the audio file/URL total duration using audio_service package?

I am using just_audio & audio_service packages to implement a playlist music player, I am facing an issue with getting the total duration of each audio file/URL, I didn't pass duration to "MediaItem instance", so how to get duration without pass the duration to MediaItem.
I will add duration as null or didn't initialize it.
MediaItem(
id: 'https://s3.amazonaws.com/scifri-episodes/scifri20181123-episode.mp3',
album: "Science Friday",
title: "A Salute To Head-Scratching Science",
artist: "Science Friday and WNYC Studios",
// duration: const Duration(milliseconds: 5739820,
or
duration: null
),
),
Example:
import 'dart:async';
import 'package:audio_service/audio_service.dart';
import 'package:audio_service_example/common.dart';
import 'package:audio_session/audio_session.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:just_audio/just_audio.dart';
import 'package:rxdart/rxdart.dart';
// You might want to provide this using dependency injection rather than a
// global variable.
late AudioPlayerHandler _audioHandler;
Future<void> main() async {
_audioHandler = await AudioService.init(
builder: () => AudioPlayerHandlerImpl(),
config: const AudioServiceConfig(
androidNotificationChannelId: 'com.ryanheise.myapp.channel.audio',
androidNotificationChannelName: 'Audio playback',
androidNotificationOngoing: true,
),
);
runApp(MyApp());
}
/// The app widget
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Audio Service Demo',
theme: ThemeData(primarySwatch: Colors.blue),
home: MainScreen3(),
);
}
}
/// The main screen.
class MainScreen3 extends StatelessWidget {
Stream<Duration> get _bufferedPositionStream => _audioHandler.playbackState
.map((state) => state.bufferedPosition)
.distinct();
Stream<Duration?> get _durationStream =>
_audioHandler.mediaItem.map((item) => item?.duration).distinct();
Stream<PositionData> get _positionDataStream =>
Rx.combineLatest3<Duration, Duration, Duration?, PositionData>(
AudioService.position,
_bufferedPositionStream,
_durationStream,
(position, bufferedPosition, duration) => PositionData(
position, bufferedPosition, duration ?? Duration.zero));
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
// MediaItem display
Expanded(
child: StreamBuilder<MediaItem?>(
stream: _audioHandler.mediaItem,
builder: (context, snapshot) {
final mediaItem = snapshot.data;
if (mediaItem == null) return const SizedBox();
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
if (mediaItem.artUri != null)
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Center(
child: Image.network('${mediaItem.artUri!}'),
),
),
),
Text(mediaItem.album ?? '',
style: Theme.of(context).textTheme.headline6),
Text(mediaItem.title),
],
);
},
),
),
// Playback controls
ControlButtons(_audioHandler),
// A seek bar.
StreamBuilder<PositionData>(
stream: _positionDataStream,
builder: (context, snapshot) {
final positionData = snapshot.data ??
PositionData(Duration.zero, Duration.zero, Duration.zero);
return SeekBar(
duration: positionData.duration,
position: positionData.position,
onChangeEnd: (newPosition) {
_audioHandler.seek(newPosition);
},
);
},
),
const SizedBox(height: 8.0),
// Repeat/shuffle controls
Row(
children: [
StreamBuilder<AudioServiceRepeatMode>(
stream: _audioHandler.playbackState
.map((state) => state.repeatMode)
.distinct(),
builder: (context, snapshot) {
final repeatMode =
snapshot.data ?? AudioServiceRepeatMode.none;
const icons = [
Icon(Icons.repeat, color: Colors.grey),
Icon(Icons.repeat, color: Colors.orange),
Icon(Icons.repeat_one, color: Colors.orange),
];
const cycleModes = [
AudioServiceRepeatMode.none,
AudioServiceRepeatMode.all,
AudioServiceRepeatMode.one,
];
final index = cycleModes.indexOf(repeatMode);
return IconButton(
icon: icons[index],
onPressed: () {
_audioHandler.setRepeatMode(cycleModes[
(cycleModes.indexOf(repeatMode) + 1) %
cycleModes.length]);
},
);
},
),
Expanded(
child: Text(
"Playlist",
style: Theme.of(context).textTheme.headline6,
textAlign: TextAlign.center,
),
),
StreamBuilder<bool>(
stream: _audioHandler.playbackState
.map((state) =>
state.shuffleMode == AudioServiceShuffleMode.all)
.distinct(),
builder: (context, snapshot) {
final shuffleModeEnabled = snapshot.data ?? false;
return IconButton(
icon: shuffleModeEnabled
? const Icon(Icons.shuffle, color: Colors.orange)
: const Icon(Icons.shuffle, color: Colors.grey),
onPressed: () async {
final enable = !shuffleModeEnabled;
await _audioHandler.setShuffleMode(enable
? AudioServiceShuffleMode.all
: AudioServiceShuffleMode.none);
},
);
},
),
],
),
// Playlist
Container(
height: 240.0,
child: StreamBuilder<QueueState>(
stream: _audioHandler.queueState,
builder: (context, snapshot) {
final queueState = snapshot.data ?? QueueState.empty;
final queue = queueState.queue;
return ReorderableListView(
onReorder: (int oldIndex, int newIndex) {
if (oldIndex < newIndex) newIndex--;
_audioHandler.moveQueueItem(oldIndex, newIndex);
},
children: [
for (var i = 0; i < queue.length; i++)
Dismissible(
key: ValueKey(queue[i].id),
background: Container(
color: Colors.redAccent,
alignment: Alignment.centerRight,
child: const Padding(
padding: EdgeInsets.only(right: 8.0),
child: Icon(Icons.delete, color: Colors.white),
),
),
onDismissed: (dismissDirection) {
_audioHandler.removeQueueItemAt(i);
},
child: Material(
color: i == queueState.queueIndex
? Colors.grey.shade300
: null,
child: ListTile(
title: Text(queue[i].title),
onTap: () => _audioHandler.skipToQueueItem(i),
),
),
),
],
);
},
),
),
],
),
),
);
}
}
class ControlButtons extends StatelessWidget {
final AudioPlayerHandler audioHandler;
ControlButtons(this.audioHandler);
#override
Widget build(BuildContext context) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: const Icon(Icons.volume_up),
onPressed: () {
showSliderDialog(
context: context,
title: "Adjust volume",
divisions: 10,
min: 0.0,
max: 1.0,
value: audioHandler.volume.value,
stream: audioHandler.volume,
onChanged: audioHandler.setVolume,
);
},
),
StreamBuilder<QueueState>(
stream: audioHandler.queueState,
builder: (context, snapshot) {
final queueState = snapshot.data ?? QueueState.empty;
return IconButton(
icon: const Icon(Icons.skip_previous),
onPressed:
queueState.hasPrevious ? audioHandler.skipToPrevious : null,
);
},
),
StreamBuilder<PlaybackState>(
stream: audioHandler.playbackState,
builder: (context, snapshot) {
final playbackState = snapshot.data;
final processingState = playbackState?.processingState;
final playing = playbackState?.playing;
if (processingState == AudioProcessingState.loading ||
processingState == AudioProcessingState.buffering) {
return Container(
margin: const EdgeInsets.all(8.0),
width: 64.0,
height: 64.0,
child: const CircularProgressIndicator(),
);
} else if (playing != true) {
return IconButton(
icon: const Icon(Icons.play_arrow),
iconSize: 64.0,
onPressed: audioHandler.play,
);
} else {
return IconButton(
icon: const Icon(Icons.pause),
iconSize: 64.0,
onPressed: audioHandler.pause,
);
}
},
),
StreamBuilder<QueueState>(
stream: audioHandler.queueState,
builder: (context, snapshot) {
final queueState = snapshot.data ?? QueueState.empty;
return IconButton(
icon: const Icon(Icons.skip_next),
onPressed: queueState.hasNext ? audioHandler.skipToNext : null,
);
},
),
StreamBuilder<double>(
stream: audioHandler.speed,
builder: (context, snapshot) => IconButton(
icon: Text("${snapshot.data?.toStringAsFixed(1)}x",
style: const TextStyle(fontWeight: FontWeight.bold)),
onPressed: () {
showSliderDialog(
context: context,
title: "Adjust speed",
divisions: 10,
min: 0.5,
max: 1.5,
value: audioHandler.speed.value,
stream: audioHandler.speed,
onChanged: audioHandler.setSpeed,
);
},
),
),
],
);
}
}
class QueueState {
static final QueueState empty =
const QueueState([], 0, [], AudioServiceRepeatMode.none);
final List<MediaItem> queue;
final int? queueIndex;
final List<int>? shuffleIndices;
final AudioServiceRepeatMode repeatMode;
const QueueState(
this.queue, this.queueIndex, this.shuffleIndices, this.repeatMode);
bool get hasPrevious =>
repeatMode != AudioServiceRepeatMode.none || (queueIndex ?? 0) > 0;
bool get hasNext =>
repeatMode != AudioServiceRepeatMode.none ||
(queueIndex ?? 0) + 1 < queue.length;
List<int> get indices =>
shuffleIndices ?? List.generate(queue.length, (i) => i);
}
/// An [AudioHandler] for playing a list of podcast episodes.
///
/// This class exposes the interface and not the implementation.
abstract class AudioPlayerHandler implements AudioHandler {
Stream<QueueState> get queueState;
Future<void> moveQueueItem(int currentIndex, int newIndex);
ValueStream<double> get volume;
Future<void> setVolume(double volume);
ValueStream<double> get speed;
}
/// The implementation of [AudioPlayerHandler].
///
/// This handler is backed by a just_audio player. The player's effective
/// sequence is mapped onto the handler's queue, and the player's state is
/// mapped onto the handler's state.
class AudioPlayerHandlerImpl extends BaseAudioHandler
with SeekHandler
implements AudioPlayerHandler {
// ignore: close_sinks
final BehaviorSubject<List<MediaItem>> _recentSubject =
BehaviorSubject.seeded(<MediaItem>[]);
final _mediaLibrary = MediaLibrary();
final _player = AudioPlayer();
final _playlist = ConcatenatingAudioSource(children: []);
#override
final BehaviorSubject<double> volume = BehaviorSubject.seeded(1.0);
#override
final BehaviorSubject<double> speed = BehaviorSubject.seeded(1.0);
final _mediaItemExpando = Expando<MediaItem>();
/// A stream of the current effective sequence from just_audio.
Stream<List<IndexedAudioSource>> get _effectiveSequence => Rx.combineLatest3<
List<IndexedAudioSource>?,
List<int>?,
bool,
List<IndexedAudioSource>?>(_player.sequenceStream,
_player.shuffleIndicesStream, _player.shuffleModeEnabledStream,
(sequence, shuffleIndices, shuffleModeEnabled) {
if (sequence == null) return [];
if (!shuffleModeEnabled) return sequence;
if (shuffleIndices == null) return null;
if (shuffleIndices.length != sequence.length) return null;
return shuffleIndices.map((i) => sequence[i]).toList();
}).whereType<List<IndexedAudioSource>>();
/// Computes the effective queue index taking shuffle mode into account.
int? getQueueIndex(
int? currentIndex, bool shuffleModeEnabled, List<int>? shuffleIndices) {
final effectiveIndices = _player.effectiveIndices ?? [];
final shuffleIndicesInv = List.filled(effectiveIndices.length, 0);
for (var i = 0; i < effectiveIndices.length; i++) {
shuffleIndicesInv[effectiveIndices[i]] = i;
}
return (shuffleModeEnabled &&
((currentIndex ?? 0) < shuffleIndicesInv.length))
? shuffleIndicesInv[currentIndex ?? 0]
: currentIndex;
}
/// A stream reporting the combined state of the current queue and the current
/// media item within that queue.
#override
Stream<QueueState> get queueState =>
Rx.combineLatest3<List<MediaItem>, PlaybackState, List<int>, QueueState>(
queue,
playbackState,
_player.shuffleIndicesStream.whereType<List<int>>(),
(queue, playbackState, shuffleIndices) => QueueState(
queue,
playbackState.queueIndex,
playbackState.shuffleMode == AudioServiceShuffleMode.all
? shuffleIndices
: null,
playbackState.repeatMode,
)).where((state) =>
state.shuffleIndices == null ||
state.queue.length == state.shuffleIndices!.length);
#override
Future<void> setShuffleMode(AudioServiceShuffleMode mode) async {
final enabled = mode == AudioServiceShuffleMode.all;
if (enabled) {
await _player.shuffle();
}
playbackState.add(playbackState.value.copyWith(shuffleMode: mode));
await _player.setShuffleModeEnabled(enabled);
}
#override
Future<void> setRepeatMode(AudioServiceRepeatMode repeatMode) async {
playbackState.add(playbackState.value.copyWith(repeatMode: repeatMode));
await _player.setLoopMode(LoopMode.values[repeatMode.index]);
}
#override
Future<void> setSpeed(double speed) async {
this.speed.add(speed);
await _player.setSpeed(speed);
}
#override
Future<void> setVolume(double volume) async {
this.volume.add(volume);
await _player.setVolume(volume);
}
AudioPlayerHandlerImpl() {
_init();
}
Future<void> _init() async {
final session = await AudioSession.instance;
await session.configure(const AudioSessionConfiguration.speech());
// Broadcast speed changes. Debounce so that we don't flood the notification
// with updates.
speed.debounceTime(const Duration(milliseconds: 250)).listen((speed) {
playbackState.add(playbackState.value.copyWith(speed: speed));
});
// Load and broadcast the initial queue
await updateQueue(_mediaLibrary.items[MediaLibrary.albumsRootId]!);
// For Android 11, record the most recent item so it can be resumed.
mediaItem
.whereType<MediaItem>()
.listen((item) => _recentSubject.add([item]));
// Broadcast media item changes.
Rx.combineLatest4<int?, List<MediaItem>, bool, List<int>?, MediaItem?>(
_player.currentIndexStream,
queue,
_player.shuffleModeEnabledStream,
_player.shuffleIndicesStream,
(index, queue, shuffleModeEnabled, shuffleIndices) {
final queueIndex =
getQueueIndex(index, shuffleModeEnabled, shuffleIndices);
return (queueIndex != null && queueIndex < queue.length)
? queue[queueIndex]
: null;
}).whereType<MediaItem>().distinct().listen(mediaItem.add);
// Propagate all events from the audio player to AudioService clients.
_player.playbackEventStream.listen(_broadcastState);
_player.shuffleModeEnabledStream
.listen((enabled) => _broadcastState(_player.playbackEvent));
// In this example, the service stops when reaching the end.
_player.processingStateStream.listen((state) {
if (state == ProcessingState.completed) {
stop();
_player.seek(Duration.zero, index: 0);
}
});
// Broadcast the current queue.
_effectiveSequence
.map((sequence) =>
sequence.map((source) => _mediaItemExpando[source]!).toList())
.pipe(queue);
// Load the playlist.
_playlist.addAll(queue.value.map(_itemToSource).toList());
await _player.setAudioSource(_playlist);
}
AudioSource _itemToSource(MediaItem mediaItem) {
final audioSource = AudioSource.uri(Uri.parse(mediaItem.id));
_mediaItemExpando[audioSource] = mediaItem;
return audioSource;
}
List<AudioSource> _itemsToSources(List<MediaItem> mediaItems) =>
mediaItems.map(_itemToSource).toList();
#override
Future<List<MediaItem>> getChildren(String parentMediaId,
[Map<String, dynamic>? options]) async {
switch (parentMediaId) {
case AudioService.recentRootId:
// When the user resumes a media session, tell the system what the most
// recently played item was.
return _recentSubject.value;
default:
// Allow client to browse the media library.
return _mediaLibrary.items[parentMediaId]!;
}
}
#override
ValueStream<Map<String, dynamic>> subscribeToChildren(String parentMediaId) {
switch (parentMediaId) {
case AudioService.recentRootId:
final stream = _recentSubject.map((_) => <String, dynamic>{});
return _recentSubject.hasValue
? stream.shareValueSeeded(<String, dynamic>{})
: stream.shareValue();
default:
return Stream.value(_mediaLibrary.items[parentMediaId])
.map((_) => <String, dynamic>{})
.shareValue();
}
}
#override
Future<void> addQueueItem(MediaItem mediaItem) async {
await _playlist.add(_itemToSource(mediaItem));
}
#override
Future<void> addQueueItems(List<MediaItem> mediaItems) async {
await _playlist.addAll(_itemsToSources(mediaItems));
}
#override
Future<void> insertQueueItem(int index, MediaItem mediaItem) async {
await _playlist.insert(index, _itemToSource(mediaItem));
}
#override
Future<void> updateQueue(List<MediaItem> newQueue) async {
await _playlist.clear();
await _playlist.addAll(_itemsToSources(newQueue));
}
#override
Future<void> updateMediaItem(MediaItem mediaItem) async {
final index = queue.value.indexWhere((item) => item.id == mediaItem.id);
_mediaItemExpando[_player.sequence![index]] = mediaItem;
}
#override
Future<void> removeQueueItem(MediaItem mediaItem) async {
final index = queue.value.indexOf(mediaItem);
await _playlist.removeAt(index);
}
#override
Future<void> moveQueueItem(int currentIndex, int newIndex) async {
await _playlist.move(currentIndex, newIndex);
}
#override
Future<void> skipToNext() => _player.seekToNext();
#override
Future<void> skipToPrevious() => _player.seekToPrevious();
#override
Future<void> skipToQueueItem(int index) async {
if (index < 0 || index >= _playlist.children.length) return;
// This jumps to the beginning of the queue item at [index].
_player.seek(Duration.zero,
index: _player.shuffleModeEnabled
? _player.shuffleIndices![index]
: index);
}
#override
Future<void> play() => _player.play();
#override
Future<void> pause() => _player.pause();
#override
Future<void> seek(Duration position) => _player.seek(position);
#override
Future<void> stop() async {
await _player.stop();
await playbackState.firstWhere(
(state) => state.processingState == AudioProcessingState.idle);
}
/// Broadcasts the current state to all clients.
void _broadcastState(PlaybackEvent event) {
final playing = _player.playing;
final queueIndex = getQueueIndex(
event.currentIndex, _player.shuffleModeEnabled, _player.shuffleIndices);
playbackState.add(playbackState.value.copyWith(
controls: [
MediaControl.skipToPrevious,
if (playing) MediaControl.pause else MediaControl.play,
MediaControl.stop,
MediaControl.skipToNext,
],
systemActions: const {
MediaAction.seek,
MediaAction.seekForward,
MediaAction.seekBackward,
},
androidCompactActionIndices: const [0, 1, 3],
processingState: const {
ProcessingState.idle: AudioProcessingState.idle,
ProcessingState.loading: AudioProcessingState.loading,
ProcessingState.buffering: AudioProcessingState.buffering,
ProcessingState.ready: AudioProcessingState.ready,
ProcessingState.completed: AudioProcessingState.completed,
}[_player.processingState]!,
playing: playing,
updatePosition: _player.position,
bufferedPosition: _player.bufferedPosition,
speed: _player.speed,
queueIndex: queueIndex,
));
}
}
/// Provides access to a library of media items. In your app, this could come
/// from a database or web service.
class MediaLibrary {
static const albumsRootId = 'albums';
final items = <String, List<MediaItem>>{
AudioService.browsableRootId: const [
MediaItem(
id: albumsRootId,
title: "Albums",
playable: false,
),
],
albumsRootId: [
MediaItem(
id: 'https://s3.amazonaws.com/scifri-episodes/scifri20181123-episode.mp3',
album: "Science Friday",
title: "A Salute To Head-Scratching Science",
artist: "Science Friday and WNYC Studios",
duration: const Duration(milliseconds: 5739820),
artUri: Uri.parse(
'https://media.wnyc.org/i/1400/1400/l/80/1/ScienceFriday_WNYCStudios_1400.jpg'),
),
MediaItem(
id: 'https://s3.amazonaws.com/scifri-segments/scifri201711241.mp3',
album: "Science Friday",
title: "From Cat Rheology To Operatic Incompetence",
artist: "Science Friday and WNYC Studios",
duration: const Duration(milliseconds: 2856950),
artUri: Uri.parse(
'https://media.wnyc.org/i/1400/1400/l/80/1/ScienceFriday_WNYCStudios_1400.jpg'),
),
MediaItem(
id: 'https://s3.amazonaws.com/scifri-segments/scifri202011274.mp3',
album: "Science Friday",
title: "Laugh Along At Home With The Ig Nobel Awards",
artist: "Science Friday and WNYC Studios",
duration: const Duration(milliseconds: 1791883),
artUri: Uri.parse(
'https://media.wnyc.org/i/1400/1400/l/80/1/ScienceFriday_WNYCStudios_1400.jpg'),
),
],
};
}
When AudioSource is supplied into AudioPlayer, AudioPlayer will return its total duration through durationStream.
You can simply listen to this durationStream and copy mediaItem with the duration received from durationStream before you sink it into your mediaItem stream of AudioHandler.
In the example, you can change the part where you sink MediaItem to mediaItem stream in the _init method on line 475 like below.
Before:
// Broadcast media item changes.
Rx.combineLatest4<int?, List<MediaItem>, bool, List<int>?, MediaItem?>(
_player.currentIndexStream,
queue,
_player.shuffleModeEnabledStream,
_player.shuffleIndicesStream,
(index, queue, shuffleModeEnabled, shuffleIndices) {
final queueIndex =
getQueueIndex(index, shuffleModeEnabled, shuffleIndices);
return (queueIndex != null && queueIndex < queue.length)
? queue[queueIndex]
: null;
}).whereType<MediaItem>().distinct().listen(mediaItem.add);
After:
// Broadcast media item changes.
Rx.combineLatest5<int?, List<MediaItem>, bool, List<int>?, Duration?,
MediaItem?>(
_player.currentIndexStream,
queue,
_player.shuffleModeEnabledStream,
_player.shuffleIndicesStream,
_player.durationStream, // <- add listening to durationStream here
(index, queue, shuffleModeEnabled, shuffleIndices, duration) {
final queueIndex =
getQueueIndex(index, shuffleModeEnabled, shuffleIndices);
return (queueIndex != null && queueIndex < queue.length)
? queue[queueIndex].copyWith(duration: duration) // <- sink mediaItem provided with duration
: null;
}).whereType<MediaItem>().distinct().listen(mediaItem.add);

I have one issue from migrating my flutter app to null_safety

I migrated my application to null-safety. I had some errors and was able to fix all but one last one.
This page is a calendar page and the part where the error occurs is when I am building the calendar. I am using the table_calendar plugin.
I added the "?" after defining the type of function for _getEventsFroDay(). I don't know if this is the right way to do this.
The argument type 'List? Function(DateTime)' can't be assigned to the parameter type 'List Function(DateTime)?'.
This is the function:
List<dynamic>? _getEventsForDay(DateTime day) {
// kEvents is a linkedHashMap
for (int i = 0; i < eventDoc.length; i++ ) {
DateTime eventDate = eventDoc[i].eventDate;
DateTime eventDateUTC = eventDate.toUtc();
if (day.year == eventDate.year && day.day == eventDate.day && day.month == eventDate.month) {
List<dynamic> eventList = [];
eventList.add(eventDoc[i].agencyId);
eventList.add(eventDoc[i].agentId);
eventList.add(eventDoc[i].eventDate);
eventList.add(eventDoc[i].eventDescription);
eventList.add(eventDoc[i].eventDuration);
eventList.add(eventDoc[i].eventName);
eventList.add(eventDoc[i].eventStartTime);
//print('kEvents: $kEvents');
return kEvents.putIfAbsent(eventDateUTC, () => eventList);
}
}
}
This is where the error is occuring:
Widget _buildTableCalendar() {
return TableCalendar(
eventLoader: _getEventsForDay, <<<< ERROR HERE
Here is the error message after I remove the "?" from List? _getEventsForDay(DateTime day) {.
Here is the full .dart file:
// Example holidays
final Map<DateTime, List> _holidays = {
DateTime(2020, 1, 1): ['New Year\'s Day'],
DateTime(2020, 1, 6): ['Epiphany'],
DateTime(2020, 2, 14): ['Valentine\'s Day'],
DateTime(2020, 4, 21): ['Easter Sunday'],
DateTime(2020, 4, 22): ['Easter Monday'],
};
final kNow = DateTime.now();
final kFirstDay = DateTime(kNow.year, kNow.month - 3, kNow.day);
final kLastDay = DateTime(kNow.year, kNow.month + 3, kNow.day);
final eventsRef = FirebaseFirestore.instance.collection('agency').doc(globals.agencyId).collection('event');
final _db = FirebaseFirestore.instance;
final _firestoreService = FirestoreService();
bool showSpinner = false;
DateTime? _selectedDay;
DateTime _focusedDay = DateTime.now();
LinkedHashMap<DateTime, List<Event>> kEvents = LinkedHashMap<DateTime, List<Event>>();
class AppointmentCalendarScreen extends StatefulWidget {
AppointmentCalendarScreen({Key? key, this.title}) : super(key: key);
final String? title;
#override
_AppointmentCalendarScreenState createState() => _AppointmentCalendarScreenState();
}
class _AppointmentCalendarScreenState extends State<AppointmentCalendarScreen> with TickerProviderStateMixin {
late final ValueNotifier<List<Event>> _selectedEvents;
Map<DateTime, List>? _selectedEventsMap;
late StreamController<Map<DateTime, List>> _streamController;
late var eventDoc;
#override
void initState() {
super.initState();
_streamController = StreamController();
_selectedDay = _focusedDay;
_selectedEvents = ValueNotifier(_getEventsForDay(_selectedDay!));
}
#override
void dispose() {
_selectedEvents.dispose();
_streamController.close();
super.dispose();
}
List<Event> _getEventsForDay(DateTime day) {
// kEvents is a linkedHashMap
for (int i = 0; i < eventDoc.length; i++ ) {
DateTime eventDate = eventDoc[i].eventDate;
DateTime eventDateUTC = eventDate.toUtc();
if (day.year == eventDate.year && day.day == eventDate.day && day.month == eventDate.month) {
List<Event> eventList = [];
eventList.add(eventDoc[i].agencyId);
eventList.add(eventDoc[i].agentId);
eventList.add(eventDoc[i].eventDate);
eventList.add(eventDoc[i].eventDescription);
eventList.add(eventDoc[i].eventDuration);
eventList.add(eventDoc[i].eventName);
eventList.add(eventDoc[i].eventStartTime);
//print('kEvents: $kEvents');
return (kEvents.putIfAbsent(eventDateUTC, () => eventList))??[];
}
}
}
void _onDaySelected(DateTime selectedDay, DateTime focusedDay) {
if (!isSameDay(_selectedDay, selectedDay)) {
setState(() {
_selectedDay = selectedDay;
_focusedDay = focusedDay;
});
_selectedEvents.value = _getEventsForDay(selectedDay);
}
}
void _onVisibleDaysChanged(DateTime first, DateTime last,
CalendarFormat format) {
}
void _onCalendarCreated(DateTime first, DateTime last,
CalendarFormat format) {
}
#override
Widget build(BuildContext context) {
final eventProvider = Provider.of<EventProvider>(context);
FirebaseFirestore _db = FirebaseFirestore.instance;
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
title: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset('assets/images/Appbar_logo.png',
fit: BoxFit.cover, height: 56),
],
),
),
backgroundColor: Colors.white,
resizeToAvoidBottomInset: false,
body: Column(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
StreamBuilder(
stream: _db.collection('agency').doc(globals.agencyId).collection('event')
.where('eventDate', isGreaterThanOrEqualTo: kFirstDay)
.where('eventDate', isLessThanOrEqualTo: kLastDay)
.snapshots().map((snapshot) => snapshot.docs
.map((document) => Event.fromFirestore(document.data()))
.toList()),
builder: (context, AsyncSnapshot <List<Event>> eventsSnapShot) {
if (eventsSnapShot.hasData) {
return _buildTableCalendar();
} else {
return CircularProgressIndicator();
}
},
),
const SizedBox(height: 8.0),
//_buildButtons(),
ElevatedButton(
onPressed: () async {
setState(() {
showSpinner = true;
});
try {
globals.newAgency = true;
Navigator.of(context).pushReplacement(MaterialPageRoute(
builder: (context) => AddEventScreen()));
setState(() {
showSpinner = false;
});
} catch (e) {
// todo: add better error handling
print(e);
}
},
child: Text('Add Event'),
),
const SizedBox(height: 8.0),
Expanded(child: _buildEventList()),
],
),
);
}
// Simple TableCalendar configuration (using Styles)
Widget _buildTableCalendar() {
return TableCalendar(
firstDay: kFirstDay,
lastDay: kLastDay,
focusedDay: _focusedDay,
selectedDayPredicate: (day) => isSameDay(_selectedDay, day),
locale: 'en_US',
eventLoader: (day) {
return _getEventsForDay(day);
},
startingDayOfWeek: StartingDayOfWeek.sunday,
calendarStyle: CalendarStyle(
isTodayHighlighted: true,
selectedDecoration: BoxDecoration(color: Colors.deepOrange[400]),
todayDecoration: BoxDecoration(color: Colors.deepOrange[200]),
markerDecoration: BoxDecoration(color: Colors.deepPurpleAccent),
outsideDaysVisible: false,
),
headerStyle: HeaderStyle(
formatButtonTextStyle:
TextStyle().copyWith(color: Colors.white, fontSize: 15.0),
formatButtonDecoration: BoxDecoration(
color: Colors.deepOrange[400],
borderRadius: BorderRadius.circular(16.0),
),
),
onDaySelected: (selectedDay, focusedDay) {
setState(() {
_selectedDay = selectedDay;
_focusedDay = focusedDay; // update `_focusedDay` here as well
});
},
onPageChanged: (focusedDay) {
_focusedDay = focusedDay;
},
);
}
Widget _buildHolidaysMarker() {
return Icon(
Icons.add_box,
size: 20.0,
color: Colors.blueGrey[800],
);
}
Widget _buildEventList() {
final _db = FirebaseFirestore.instance;
return Container(
child: StreamBuilder<QuerySnapshot>(
//stream: FirestoreService().getEventStream(_focusedDay),
stream: _db.collection('agency').doc(globals.agencyId).collection(
'event').where('eventDate', isEqualTo: _focusedDay).snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(
child: const Text(
'Loading...',
style: TextStyle(
fontSize: 20, fontWeight: FontWeight.bold),
));
} else {
var doc = snapshot.data!.docs;
return new ListView.builder(
itemCount: doc.length,
itemBuilder: (BuildContext context, int index) {
Event _event = Event.fromFirestore(
doc[index].data() as Map<String, dynamic>);
return ListTile(
isThreeLine: true,
title: Text(
'${_event.eventName ?? 'n/a'}',
style: TextStyle(
fontWeight: FontWeight.w900,
color: Colors.blueAccent),
),
subtitle: Text.rich(TextSpan(
text:
'${DateFormat('EE MM-dd-yyyy').format(_event.eventDate!) ?? 'n/a'}\n'
'${DateFormat('h:mm a').format(_event.eventStartTime!) ?? 'n/a'}, '
'Duration: ${_event.eventDuration ?? 'n/a'} minutes',
children: <TextSpan>[
TextSpan(
text:
'\n${_event.eventDescription ?? 'n/a'}',
style: TextStyle(
fontWeight: FontWeight.w900,
color: Colors.blueGrey),
)
])),
//trailing: Text('MLS#: ${_event.mlsNumber ?? 'n/a'}'),
onTap: () {
globals.newTrxn = false;
/*
Navigator.of(context).push(MaterialPageRoute(
builder: (context) =>
AddEventScreen(
doc[index].data())));
*/
},
);
}
);
}
},
),
);
}
Map<DateTime, List>? convertToMap(List<Event> item) {
Map<DateTime, List>? result;
for (int i = 0; i < item.length; i++) {
Event data = item[i];
//get the date and convert it to a DateTime variable
//DateTime currentDate = DateFormat('MM-dd-yyyy - kk:mm').format(data.eventDate);
DateTime currentDate = DateTime(data.eventDate!.year, data.eventDate!.month, data.eventDate!.day, data.eventDate!.hour, data.eventDate!.minute);
List eventNames = [];
//add the event name to the the eventNames list for the current date.
//search for another event with the same date and populate the eventNames List.
for (int j = 0; j < item.length; j++) {
//create temp calendarItemData object.
Event temp = item[j];
//establish that the temp date is equal to the current date
if (data.eventDate == temp.eventDate) {
//add the event name to the event List.
eventNames.add(temp.eventName);
} //else continue
}
//add the date and the event to the map if the date is not contained in the map
if (result == null) {
result = {
currentDate: eventNames
};
} else {
result[currentDate] = eventNames;
}
return result;
}
}
}
If your list is Event type then you should return List<Event>? Instead of List<dynamic>? from _getEventsForDay function. Hope this will solve your problem.
Edited:
My bad. I have read the documentation and it says, you can give the value of eventloader either a null or a function which return type should be a non null list.
So, in your case you are returning a List? which means the function return type can be null. That's why you are getting error.
List<Event> _getEventsForDay(DateTime day) {
// kEvents is a linkedHashMap
for (int i = 0; i < eventDoc.length; i++ ) {
DateTime eventDate = eventDoc[i].eventDate;
DateTime eventDateUTC = eventDate.toUtc();
if (day.year == eventDate.year && day.day == eventDate.day && day.month == eventDate.month) {
List<dynamic> eventList = [];
eventList.add(eventDoc[i].agencyId);
eventList.add(eventDoc[i].agentId);
eventList.add(eventDoc[i].eventDate);
eventList.add(eventDoc[i].eventDescription);
eventList.add(eventDoc[i].eventDuration);
eventList.add(eventDoc[i].eventName);
eventList.add(eventDoc[i].eventStartTime);
//print('kEvents: $kEvents');
return kEvents.putIfAbsent(eventDateUTC, () => eventList);
}
}
}
Second problem:
Now this error is telling that kEvents.putIfAbsent(eventDateUTC, () => eventList); may return null. Which is not acceptable because our function will not return any null value now. In this case we can use null check operator like this.
return (kEvents.putIfAbsent(eventDateUTC, () => eventList))??[];
Hope this will solve your problem.
List<Event> _getEventsForDay(DateTime day) {
// kEvents is a linkedHashMap
for (int i = 0; i < eventDoc.length; i++ ) {
DateTime eventDate = eventDoc[i].eventDate;
DateTime eventDateUTC = eventDate.toUtc();
if (day.year == eventDate.year && day.day == eventDate.day && day.month == eventDate.month) {
List<dynamic> eventList = [];
eventList.add(eventDoc[i].agencyId);
eventList.add(eventDoc[i].agentId);
eventList.add(eventDoc[i].eventDate);
eventList.add(eventDoc[i].eventDescription);
eventList.add(eventDoc[i].eventDuration);
eventList.add(eventDoc[i].eventName);
eventList.add(eventDoc[i].eventStartTime);
//print('kEvents: $kEvents');
return kEvents.putIfAbsent(eventDateUTC, () => eventList);
}
}
}
Since your for has an if clause inside of it, and the eventDoc.length can be empty, there is a possibility of your function never reaching the return statement:
return kEvents.putIfAbsent(eventDateUTC, () => eventList);
So what the IDE is telling is that you need to add a return to the end of the function, so it can return null, if eventDoc.length < 1 or the if clause is never fulfilled.
To solve your problem add:
return [];
After your for loop, at the end of the function:
List<Event> _getEventsForDay(DateTime day) {
// kEvents is a linkedHashMap
for (int i = 0; i < eventDoc.length; i++ ) {
DateTime eventDate = eventDoc[i].eventDate;
DateTime eventDateUTC = eventDate.toUtc();
if (day.year == eventDate.year && day.day == eventDate.day && day.month == eventDate.month) {
List<dynamic> eventList = [];
eventList.add(eventDoc[i].agencyId);
eventList.add(eventDoc[i].agentId);
eventList.add(eventDoc[i].eventDate);
eventList.add(eventDoc[i].eventDescription);
eventList.add(eventDoc[i].eventDuration);
eventList.add(eventDoc[i].eventName);
eventList.add(eventDoc[i].eventStartTime);
//print('kEvents: $kEvents');
return kEvents.putIfAbsent(eventDateUTC, () => eventList);
}
}
return [];
}
You can use fork of flutter table_calendar with null safety: https://pub.dev/packages/table_calendar_null_safe

Type mismatch in table_calendar

I am adding the table_calendar plugin to my flutter app. Most things are working so far thanks to the help I have been getting from y'all.
Here is the next issue I am getting. When I click on a date on the calendar I get the following error and then nothing happens.
======== Exception caught by gesture ===============================================================
The following _TypeError was thrown while handling a gesture:
type '_MapStream<QuerySnapshot<Map<String, dynamic>>, List<Event>>' is not a subtype of type 'Map<DateTime, List<dynamic>>'
When the exception was thrown, this was the stack:
#0 _AppointmentCalendarScreenState._onDaySelected.<anonymous closure> (package:tonnah/screens/appointment_calendar.dart:73:29)
Here is the code at line 73
void _onDaySelected(DateTime day, List events, List holidays) {
_eventsStream = _firestoreService.getEventStream(day);
setState(() {
_streamController.add(_eventsStream); <<<<< LINE 73
//_selectedEvents = _eventsStream;
});
}
Here is where I populate _eventsStream:
_eventsStream = _firestoreService.getEventStream(_selectedDay);
Stream<List<Event>> getEventStream(DateTime dateTime) {
return _db.collection('agency').doc(globals.agencyId).collection('event')
.where('eventDate', isGreaterThanOrEqualTo: Timestamp.fromDate(dateTime))
.snapshots().map((snapshot) => snapshot.docs
.map((document) => Event.fromFirestore(document.data()))
.toList());
}
It is also used here to put the event indicator on the calendar:
StreamBuilder(
//stream: _firestoreService.getEventStream(_selectedDay),
stream: _db.collection('agency').doc(globals.agencyId).collection('event')
.where('eventDate', isGreaterThanOrEqualTo: Timestamp.fromDate(_selectedDay))
.snapshots().map((snapshot) => snapshot.docs
.map((document) => Event.fromFirestore(document.data()))
.toList()),
builder: (context, snapshot) {
if (snapshot.hasData) {
List<Event> snapData;
//return snapData = snapshot.data;
_eventsStream = snapshot.data;
_eventsMap = convertToMap(_eventsStream);
//_selectedEventsMap = _eventsMap[_selectedDay] ?? [];
return _buildTableCalendar();
} else {
return CircularProgressIndicator();
}
},
),
I know there is a type difference but how do I make them match?
Here is the code from the calendar page:
// Example holidays
final Map<DateTime, List> _holidays = {
DateTime(2020, 1, 1): ['New Year\'s Day'],
DateTime(2020, 1, 6): ['Epiphany'],
DateTime(2020, 2, 14): ['Valentine\'s Day'],
DateTime(2020, 4, 21): ['Easter Sunday'],
DateTime(2020, 4, 22): ['Easter Monday'],
};
//final eventsRef = FirebaseFirestore.instance.collection('agency').doc(globals.agencyId).collection('event');
final _firestoreService = FirestoreService();
bool showSpinner = false;
var _eventsStream;
var _eventsMap;
final _selectedDay = DateTime.now();
class AppointmentCalendarScreen extends StatefulWidget {
AppointmentCalendarScreen({Key key, this.title}) : super(key: key);
final String title;
#override
_AppointmentCalendarScreenState createState() => _AppointmentCalendarScreenState();
}
class _AppointmentCalendarScreenState extends State<AppointmentCalendarScreen> with TickerProviderStateMixin {
//var _eventsList;
//Map<DateTime, List> _selectedEventsMap;
AnimationController _animationController;
CalendarController _calendarController;
StreamController<Map<DateTime, List>> _streamController;
#override
void initState() {
super.initState();
_streamController = StreamController();
_eventsStream = _firestoreService.getEventStream(_selectedDay);
_calendarController = CalendarController();
_animationController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 400),
);
_animationController.forward();
}
#override
void dispose() {
_animationController.dispose();
_calendarController.dispose();
_streamController.close();
super.dispose();
}
void _onDaySelected(DateTime day, List events, List holidays) {
_eventsStream = _firestoreService.getEventStream(day);
setState(() {
_streamController.add(_eventsStream);
//_selectedEvents = _eventsStream;
});
}
void _onVisibleDaysChanged(DateTime first, DateTime last,
CalendarFormat format) {
}
void _onCalendarCreated(DateTime first, DateTime last,
CalendarFormat format) {
}
#override
Widget build(BuildContext context) {
final eventProvider = Provider.of<EventProvider>(context);
FirebaseFirestore _db = FirebaseFirestore.instance;
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
title: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset('assets/images/Appbar_logo.png',
fit: BoxFit.cover, height: 56),
],
),
),
backgroundColor: Colors.white,
resizeToAvoidBottomInset: false,
body: Column(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
StreamBuilder(
stream: _firestoreService.getEventStream(_selectedDay),
builder: (context, snapshot) {
if (snapshot.hasData) {
List<Event> snapData;
_eventsStream = snapshot.data;
_eventsMap = convertToMap(_eventsStream);
//_selectedEventsMap = _eventsMap[_selectedDay] ?? [];
return _buildTableCalendar();
} else {
return CircularProgressIndicator();
}
},
),
const SizedBox(height: 8.0),
//_buildButtons(),
ElevatedButton(
onPressed: () async {
setState(() {
showSpinner = true;
});
try {
globals.newAgency = true;
//eventProvider.saveEvent();
Navigator.of(context).pushReplacement(MaterialPageRoute(
builder: (context) => AddEventScreen()));
setState(() {
showSpinner = false;
});
} catch (e) {
// todo: add better error handling
print(e);
}
},
child: Text('Add Event'),
),
const SizedBox(height: 8.0),
Expanded(child: _buildEventList()),
],
),
);
}
// Simple TableCalendar configuration (using Styles)
Widget _buildTableCalendar() {
return TableCalendar(
calendarController: _calendarController,
locale: 'en_US',
events: _eventsMap,
holidays: _holidays,
startingDayOfWeek: StartingDayOfWeek.sunday,
calendarStyle: CalendarStyle(
selectedColor: Colors.deepOrange[400],
todayColor: Colors.deepOrange[200],
markersColor: Colors.deepPurpleAccent,
outsideDaysVisible: false,
),
headerStyle: HeaderStyle(
formatButtonTextStyle:
TextStyle().copyWith(color: Colors.white, fontSize: 15.0),
formatButtonDecoration: BoxDecoration(
color: Colors.deepOrange[400],
borderRadius: BorderRadius.circular(16.0),
),
),
onDaySelected: _onDaySelected,
onVisibleDaysChanged: _onVisibleDaysChanged,
onCalendarCreated: _onCalendarCreated,
);
}
Widget _buildHolidaysMarker() {
return Icon(
Icons.add_box,
size: 20.0,
color: Colors.blueGrey[800],
);
}
Widget _buildEventList() {
final _db = FirebaseFirestore.instance;
return Container(
child: StreamBuilder<QuerySnapshot>(
//stream: FirestoreService().getEvent(),
stream: _db.collection('agency').doc(globals.agencyId).collection(
'event').snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(
child: const Text(
'Loading...',
style: TextStyle(
fontSize: 20, fontWeight: FontWeight.bold),
));
} else {
var doc = snapshot.data.docs;
return new ListView.builder(
itemCount: doc.length,
itemBuilder: (BuildContext context, int index) {
Event _event = Event.fromFirestore(
doc[index].data());
return ListTile(
isThreeLine: true,
title: Text(
'${_event.eventName ?? 'n/a'}',
style: TextStyle(
fontWeight: FontWeight.w900,
color: Colors.blueAccent),
),
subtitle: Text.rich(TextSpan(
text:
//'${_event.eventName ?? 'n/a'}, '
'${_event.eventStartTime ?? 'n/a'}, '
'Duration: ${_event.eventDuration ?? 'n/a'}',
children: <TextSpan>[
TextSpan(
text:
'\n${_event.eventDescription ?? 'n/a'}',
style: TextStyle(
fontWeight: FontWeight.w900,
color: Colors.blueGrey),
)
])),
//trailing: Text('MLS#: ${_event.mlsNumber ?? 'n/a'}'),
onTap: () {
globals.newTrxn = false;
/*
Navigator.of(context).push(MaterialPageRoute(
builder: (context) =>
AddEventScreen(
doc[index].data())));
*/
},
);
}
);
}
},
),
);
}
Map<DateTime, List> convertToMap(List<Event> item) {
Map<DateTime, List> result;
for (int i = 0; i < item.length; i++) {
Event data = item[i];
//get the date and convert it to a DateTime variable
//DateTime currentDate = DateFormat('MM-dd-yyyy - kk:mm').format(data.eventDate);
DateTime currentDate = DateTime(data.eventDate.year, data.eventDate.month, data.eventDate.day, data.eventDate.hour, data.eventDate.minute);
List eventNames = [];
//add the event name to the the eventNames list for the current date.
//search for another event with the same date and populate the eventNames List.
for (int j = 0; j < item.length; j++) {
//create temp calendarItemData object.
Event temp = item[j];
//establish that the temp date is equal to the current date
if (data.eventDate == temp.eventDate) {
//add the event name to the event List.
eventNames.add(temp.eventName);
} //else continue
}
//add the date and the event to the map if the date is not contained in the map
if (result == null) {
result = {
currentDate: eventNames
};
} else {
result[currentDate] = eventNames;
}
return result;
}
}
}
You have to convert on type: Map<DateTime, List>.
Create method which will convert '_MapStream<QuerySnapshot<Map<String, dynamic>>, List>' on 'Map<DateTime, List>'
In your case: convert on 'Map<DateTime, List< "here you should add all data which you want to have in calendar view. Can be map or sth else">>
Actually, you need thre methods:
First: Convert method which I mentioned above. Method exemple below
final int _currentYearInt = DateTime.now().year;
final int _currentMonthInt =
int.parse(addZeroToNumberLowerThanTen(number: DateTime.now().month));
documentsList.forEach((doc) {
Map<DateTime, List<Shift>> _schedule = {};
// "days" is a properties with values: {"10" : [], "12": []}, where "10" is day of month"
doc['days'].forEach((k, v) {
int _shiftDay = int.parse(k);
if (calendarWithMarkedDates[
DateTime.utc(_currentYearInt, _currentMonthInt, _shiftDay)] ==
null) {
calendarWithMarkedDates[
DateTime.utc(_currentYearInt, _currentMonthInt, _shiftDay)] = [];
}
calendarWithMarkedDates[DateTime.utc(_currentYearInt, _currentMonthInt, _shiftDay)]!
.add( "your item. Can be object, map or what you want" );
});
});
Second: "calendarWithMarkedDates" is a LinkedHashMap, which holds all the marked dates. F.e. from server has response dates: 1.07, 5.07, 12.07. All dates after convert to LinkedHashMap are storing in this "calendarWithMarkedDates".
final calendarWithMarkedDates = LinkedHashMap<DateTime, List<Shift>>(
equals: isSameDay,
hashCode: getHashCode,
);
Third: "currentScheduleForDay" it is function which yu hava to invoke like a value of property "eventLoader" in TableCalendar without parameters.
It means:
good way currentScheduleForDay
bad way: currentScheduleForDay()
List<Shift> currentScheduleForDay(DateTime day) {
return currentCalendarWithMarkedDates[day] ?? [];
}
When use method currentScheduleForDay (without "()"), TableCalendar by it self made loops through all dates in month and inovke method for all day.

How to select only one checkbox from Checkbox Listtile in Flutter

Here I've one service page where I've displayed all the services, and there I can select multiple services. but I want to select only one service. I've used the checkbox Listitle for the service selection. I want that user can select only one service not multiple services at a time.
Here is code i've tried :
class _AddWalkinServiceScreenState extends State<AddWalkinServiceScreen>
with TickerProviderStateMixin {
List<int> servicesIds = [];
int selected = 0;
Map<String, bool> _selection = {};
List<BspServices.Service> selectedServices = [];
SearchBarController _controller = new SearchBarController();
String _searchText = '';
List<dynamic> finalList = new List();
List<dynamic> searchList = new List();
bool isLoading = false;
AnimationController controller;
Animation<double> animation;
#override
void initState() {
super.initState();
controller = AnimationController(
duration: const Duration(milliseconds: 500), vsync: this);
animation = CurvedAnimation(parent: controller, curve: Curves.easeInQuint);
controller.forward();
}
Widget _renderServices(AddWalkinServiceViewModel awsVm) {
List lovCountryServices = searchList.length != 0 ? searchList : finalList;
if (lovCountryServices == null || lovCountryServices.length == 0) {
return Container(
child: Center(
child: Text("No Services available for this combination"),
),
);
}
// print(lovCountryServices);
return Container(
child: finalList.length < 1
? ListTile(
leading: CircularProgressIndicator(),
)
: ListView.builder(
shrinkWrap: true,
padding: const EdgeInsets.all(8.0),
itemCount: lovCountryServices.length,
itemBuilder: (BuildContext context, int index) {
var item = lovCountryServices[
index]; // should be outside build function
List items = item['services'];
return ExpansionTile(
title: Text(item['name']),
children: List.generate(items.length, (i) {
_selection[items[i]['name']] =
_selection[items[i]['name']] ?? items[i]['isSelected'];
return CheckboxListTile(
title: Text(items[i]['name']),
value: _selection[items[i]['name']] == null
? false
: _selection[items[i]['name']],
onChanged: (val) {
setState(() {
_selection[items[i]['name']] = val;
if (val) {
servicesIds.add(items[i]['id']);
List<BspServices.Service> services =
selectedServices.where((service) {
return service.mainCategory == item['name'];
}).toList();
SubCategory subService = new SubCategory(
id: items[i]['id'],
name: items[i]['name'],
);
List<SubCategory> subCategories = [];
if (services.length < 1) {
subCategories.add(subService);
selectedServices.add(
new BspServices.Service(
mainCategory: item['name'],
mainCategoryId: item['id'],
subCategory: subCategories,
),
);
} else {
print('services in else');
print(services[0].subCategory);
subCategories = services[0].subCategory;
subCategories.add(subService);
}
} else {
servicesIds.removeWhere((service) {
return service == items[i]['id'];
});
List<BspServices.Service> services =
selectedServices.where((service) {
return service.mainCategory == item['name'];
}).toList();
services[0].subCategory.removeWhere((subService) {
return subService.id == items[i]['id'];
});
}
});
print('servicesIds after set state');
print(servicesIds);
},
);
}),
);
},
),
);
}
Widget content(BuildContext context, AddWalkinServiceViewModel awsVm) {
Orientation orientation = MediaQuery.of(context).orientation;
var colorStyles = Theming.colorstyle(context);
final appBar = SearchBar(
controller: _controller,
onQueryChanged: (String query) {
print('Search Query $query');
setState(() {
_searchText = query;
});
_searchFilter();
},
defaultBar: AppBar(
elevation: 0,
centerTitle: true,
leading: IconButton(
icon: Icon(Icons.arrow_back_ios),
onPressed: () {
Navigator.pop(context);
// NavigationHelper.navigatetoBack(context);
}),
title: Text('Select Services'),
),
);
return new Scaffold(
backgroundColor: colorStyles['primary'],
appBar: appBar,
bottomNavigationBar: bottomNavigationBar,
body: FadeTransition(
opacity: animation,
child: new Container(
margin: EdgeInsets.only(top: 10),
decoration: new BoxDecoration(
color: Colors.white,
borderRadius: new BorderRadius.only(
topLeft: const Radius.circular(50.0),
topRight: const Radius.circular(50.0),
),
),
child: isLoading ? FadeInUi() : _renderServices(awsVm),
),
),
);
}
#override
Widget build(BuildContext context) {
return new StoreConnector<AppState, AddWalkinServiceViewModel>(
converter: (Store<AppState> store) =>
AddWalkinServiceViewModel.fromStore(store),
onInit: (Store<AppState> store) {
print('store.state.servicesState.servicesByCountry');
print(store
.state.servicesState.servicesByCountry.servicesByCountry[0].name);
Map<String, dynamic> services =
store.state.servicesState.servicesByCountry.toJson();
finalList = services['servicesByCountry'];
print('finalList = $finalList');
},
builder: (BuildContext context, AddWalkinServiceViewModel awsVm) =>
content(context, awsVm),
);
}
}
UPDATED
List<Map> services = [];
List<int> selections = [];
#override
void initState() {
super.initState();
getList();
}
void getList() async {
//get data from internet/api
//for ex. I m using offline data
setState(() {
services = List.generate(
10,
(ind) => {
'name': 'Service Category $ind',
'services': ['Service 1', 'Service 2']
}).toList();
selections = List.generate(10, (ind) => -1).toList();
});
}
#override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(20),
color: Colors.white,
child: services.length < 1
? ListTile(
leading: CircularProgressIndicator(), title: Text('Loading...'))
: ListView.builder(
itemCount: services.length,
itemBuilder: (con, ind) {
return ExpansionTile(
title: Text('${services[ind]['name']}',
style: TextStyle(color: Colors.black)),
children:
List.generate(services[ind]['services'].length, (ii) {
return CheckboxListTile(
title: Text('${services[ind]['services'][ii]}',
style: TextStyle(color: Colors.green[900])),
value: selections[ind] == ii,
onChanged: (b) {
setState(() {
selections[ind] = ii;
});
});
}).toList());
}));
}