I am learning Dart and making an app where you should hear an audio playback onTap
on a piece of text (Tile
). While playback is on, if another Tile
is tapped, I want the color of the previous unfinished recording to return to default, and the new playback tile to change.
This is my app at the moment:
import 'package:flutter/material.dart';
import 'package:audioplayers/audioplayers.dart';
List<String> titles = <String>[
'class1',
'class2',
'class3',
];
List<String> sentences = <String>[
'sentence1',
'sentence2',
'sentence3',
'sentence4'
];
List<String> audio = <String>[
'1.mp3',
'2.mp3',
'3.mp3',
'4.mp3'
];
void main() => runApp(const AppBarApp());
class AppBarApp extends StatelessWidget {
const AppBarApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
colorSchemeSeed: const Color(0xff6750a4), useMaterial3: true),
home: const AppBarExample(),
);
}
}
class AppBarExample extends StatefulWidget {
const AppBarExample({super.key});
@override
State<AppBarExample> createState() => _AppBarExampleState();
}
class _AppBarExampleState extends State<AppBarExample> {
@override
Widget build(BuildContext context) {
...//styling details ...
bool? isCurrentlyPlaying;
return DefaultTabController(
...//tabbar setup details ...
children: <Widget>[
ListView.builder(
itemCount: sentences.length,
itemBuilder:(BuildContext context, int index) {
return ListTile(
tileColor: index.isOdd
? oddItemColor : evenItemColor,
title: AudioPlayerWidget(index: index,
isCurrentlyPlaying: false,
onPlay: () {setState (() {isCurrentlyPlaying = true;});},
),
);
},
),
...//other items in the ListView ...
},
),
],
),
),
);
}
}
// AudioPlayer
final audioPlayer = AudioPlayer();
class AudioPlayerWidget extends StatefulWidget {
final int index;
final VoidCallback onPlay;
final bool isCurrentlyPlaying;
AudioPlayerWidget ({Key ? key, required this.index, required this.onPlay, required this.isCurrentlyPlaying}) : super(key: key);
@override
_AudioPlayerWidgetState createState() => _AudioPlayerWidgetState();
}
class _AudioPlayerWidgetState extends State<AudioPlayerWidget> {
bool isPlaying = false;
void _playAudio() {
audioPlayer.stop();
audioPlayer.play(AssetSource('audio/${widget.index+1}.mp3'));
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
widget.onPlay();
_playAudio();
},
child: Text(sentences[widget.index],
style: TextStyle(
color: widget.isCurrentlyPlaying ? Colors.indigo.shade900 : Colors.black,
)
),
);
}
}
The logic is as follows:
iscurrentlyPlaying
by default =false
- onTap, I trigger onPlay() via
widget.onPlay
- this sets state of "iscurrentlyplaying" to "true" for the given tapped tile,
- TextStyle color is controlled by
isCurrentlyPlaying ? indigo : black
My issue is that onPlay() does not get triggered as expected, so the color remains black (default), and I am unable to diagnose why.
I previously successfully implemented alternative logic where colors change as expected if audio is allowed to play until the end. It is the interrupt logic, where I effectively want to have a value of "iscurrentlyplaying" assigned to each tile, that does not work.
2
Answers
After a day or so of hitting my head against the wall I found a way to achieve what I needed. In my original code I think I was confusing the state of
AudioPlayer
widget and theListView
widget. As a result, I was definingcurrentlyPlaying
twice, overwriting withfalse
each time.Posting my solution here:
currentlyPlayingIndex
var to track which widget is playingonPlay
as well asonStop
callback functions This allows for changing text color once playback is completed. It is also the function that is called in case of interrupting the original playback by tapping on another element in the UI. This way thestate
which controls font color can be turned both on and off easily.isCurrentlyPlaying
is onlytrue
when the tapped widget is playing, and automatically false otherwise_playAudio
function, useonPlay
rather thanwidget.onPlay
, and make use ofonStop
as wellFull code for reference
I hope this helps somebody :)
In this part of the code:
you hardcoded
isCurrentlyPlaying: false
. This should beisCurrentlyPlaying: isCurrentlyPlaying
.You should also change
to
so that it is false by default