Halo teman-teman, kita hampir di ujung seri artikel nih, sebelumnya kita kan membuat daftar surah, selanjutnya kita akan menampilkan detail surah, jadi jika klik salah satu dari daftar surah nanti akan menuju halaman detail surah.
Pada halaman detail surah nanti kita membuat tampilan detail surah, lalu membuat daftar ayat serta kita akan membuat media player untuk memutar audio.
Langkah 1 - Membuat DetailSurah
Langkah pertama kita buat file dengan nama detail_surah_page.dart
pada folder /lib
, lalu pada file tersebut kita ketik kode berikut:
import 'package:alquran_flutter/detail_surah_model.dart';
import 'package:alquran_flutter/remote_resource.dart';
import 'package:flutter/material.dart';
class DetailSurahPage extends StatefulWidget {
final int id;
const DetailSurahPage({super.key, required this.id});
@override
State<DetailSurahPage> createState() => _DetailSurahPageState();
}
class _DetailSurahPageState extends State<DetailSurahPage> {
final remoteResource = RemoteResource();
final List<Ayat> ayatList = [];
DetailSurahModel detailSurah = DetailSurahModel();
@override
void initState() {
getDetailSurah(widget.id);
super.initState();
}
void getDetailSurah(int id) async {
final result = await remoteResource.detailSurah(id);
result.fold(
(error) => {
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text('Error: $error'))),
},
(data) {
setState(() {
detailSurah = data;
ayatList.addAll(data.ayat ?? []);
});
},
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: const Icon(Icons.arrow_back_ios_new_rounded),
onPressed: () {
Navigator.pop(context);
},
),
),
body: Column(
children: [
Container(
width: double.infinity,
height: 200,
padding: const EdgeInsets.all(12),
margin: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.secondaryContainer,
borderRadius: BorderRadius.circular(20),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
detailSurah.namaLatin ?? '',
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
Text(
detailSurah.nama ?? '',
style: const TextStyle(
fontSize: 26,
fontWeight: FontWeight.bold,
),
),
],
),
Divider(
color: Colors.blueGrey.withValues(alpha: 0.5),
thickness: 1,
height: 20,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Column(
spacing: 2,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'(${detailSurah.arti})',
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
Text(
'${detailSurah.tempatTurun?.toUpperCase()} | ${detailSurah.jumlahAyat} Ayat',
),
],
),
],
),
],
),
),
Expanded(
child: ListView.builder(
itemCount: ayatList.length,
itemBuilder: (context, index) {
return cardAyat(ayatList[index]);
},
),
),
],
),
);
}
Widget cardAyat(Ayat ayat) {
return Card(
elevation: 0.5,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
margin: EdgeInsets.symmetric(vertical: 8, horizontal: 16),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 8,
children: [
Row(
spacing: 4,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Container(
padding: EdgeInsets.all(5),
decoration: BoxDecoration(
border: Border.all(
color: Theme.of(context).colorScheme.primary,
width: 1,
),
borderRadius: BorderRadius.circular(10),
),
child: Text(
'${ayat.surah}:${ayat.nomor}',
style: TextStyle(fontSize: 11),
),
),
Expanded(
child: Directionality(
textDirection: TextDirection.rtl,
child: Text(
ayat.ar ?? '',
style: const TextStyle(fontSize: 24),
),
),
),
],
),
Text('${ayat.idn}'),
],
),
),
);
}
}
Penambahan kode di atas yang pertama import beberapa package atau library yang dibutuhkan.
import 'package:alquran_flutter/detail_surah_model.dart';
import 'package:alquran_flutter/remote_resource.dart';
import 'package:flutter/material.dart';
lalu pada kelas DetailSurahPage()
kita membuat construktur dengan nama id
dengan tipe data int
, konstrutor ini digunakan untuk menerima data berniali angka.
class DetailSurahPage extends StatefulWidget {
final int id;
const DetailSurahPage({super.key, required this.id});
@override
State<DetailSurahPage> createState() => _DetailSurahPageState();
}
Lalu kita inisialisasi remoteresource dan bebeberapa model dan variabel.
class _DetailSurahPageState extends State<DetailSurahPage> {
final remoteResource = RemoteResource();
final List<Ayat> ayatList = [];
DetailSurahModel detailSurah = DetailSurahModel();
...
}
Lalu kita membuat fungsi untuk mendapatkan data dari remote service dengan nama void getDetailSurah(int id)
, lalu kita panggil pada methode initState()
, dengan memasukkan parameter id surah yang di ambil dari widget.id
void initState() {
getDetailSurah(widget.id);
super.initState();
}
void getDetailSurah(int id) async {
final result = await remoteResource.detailSurah(id);
result.fold(
(error) => {
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text('Error: $error'))),
},
(data) {
setState(() {
detailSurah = data;
ayatList.addAll(data.ayat ?? []);
});
},
);
}
lalu kita membuat tampilan kartu untuk ayat nanti dengan Widget cardAyat()
.
Widget cardAyat(Ayat ayat) {
return Card(
elevation: 0.5,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
margin: EdgeInsets.symmetric(vertical: 8, horizontal: 16),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 8,
children: [
Row(
spacing: 4,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Container(
padding: EdgeInsets.all(5),
decoration: BoxDecoration(
border: Border.all(
color: Theme.of(context).colorScheme.primary,
width: 1,
),
borderRadius: BorderRadius.circular(10),
),
child: Text(
'${ayat.surah}:${ayat.nomor}',
style: TextStyle(fontSize: 11),
),
),
Expanded(
child: Directionality(
textDirection: TextDirection.rtl,
child: Text(
ayat.ar ?? '',
style: const TextStyle(fontSize: 24),
),
),
),
],
),
Text('${ayat.idn}'),
],
),
),
);
}
Lalu kita tampilkan pada ui utama pada method build()
.
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: const Icon(Icons.arrow_back_ios_new_rounded),
onPressed: () {
Navigator.pop(context);
},
),
),
body: Column(
children: [
Container(
width: double.infinity,
height: 200,
padding: const EdgeInsets.all(12),
margin: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.secondaryContainer,
borderRadius: BorderRadius.circular(20),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
detailSurah.namaLatin ?? '',
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
Text(
detailSurah.nama ?? '',
style: const TextStyle(
fontSize: 26,
fontWeight: FontWeight.bold,
),
),
],
),
Divider(
color: Colors.blueGrey.withValues(alpha: 0.5),
thickness: 1,
height: 20,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Column(
spacing: 2,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'(${detailSurah.arti})',
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
Text(
'${detailSurah.tempatTurun?.toUpperCase()} | ${detailSurah.jumlahAyat} Ayat',
),
],
),
],
),
],
),
),
Expanded(
child: ListView.builder(
itemCount: ayatList.length,
itemBuilder: (context, index) {
return cardAyat(ayatList[index]);
},
),
),
],
),
);
}
Langkah 2 - Membuat Audio Player
Langkah selanjutnya kita akan membuat audio player untuk memutar audio dari internet, kita buka file detail_surah_page.dart
lalu rubah kode menjadi berikut;
import 'package:alquran_flutter/detail_surah_model.dart';
import 'package:alquran_flutter/remote_resource.dart';
import 'package:flutter/material.dart';
import 'package:just_audio/just_audio.dart';
class DetailSurahPage extends StatefulWidget {
final int id;
const DetailSurahPage({super.key, required this.id});
@override
State<DetailSurahPage> createState() => _DetailSurahPageState();
}
class _DetailSurahPageState extends State<DetailSurahPage> {
final remoteResource = RemoteResource();
final List<Ayat> ayatList = [];
DetailSurahModel detailSurah = DetailSurahModel();
late AudioPlayer player;
Stream<Duration> get _positionStream => player.positionStream;
Stream<Duration?> get _durationStream => player.durationStream;
@override
void dispose() {
player.dispose();
super.dispose();
}
@override
void initState() {
player = AudioPlayer();
getDetailSurah(widget.id);
super.initState();
}
void getDetailSurah(int id) async {
final result = await remoteResource.detailSurah(id);
result.fold(
(error) => {
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text('Error: $error'))),
},
(data) {
setState(() {
detailSurah = data;
ayatList.addAll(data.ayat ?? []);
});
player.setUrl(detailSurah.audio ?? '');
},
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: const Icon(Icons.arrow_back_ios_new_rounded),
onPressed: () {
Navigator.pop(context);
},
),
),
body: Column(
children: [
Container(
width: double.infinity,
height: 200,
padding: const EdgeInsets.all(12),
margin: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.secondaryContainer,
borderRadius: BorderRadius.circular(20),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
detailSurah.namaLatin ?? '',
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
Text(
detailSurah.nama ?? '',
style: const TextStyle(
fontSize: 26,
fontWeight: FontWeight.bold,
),
),
],
),
Divider(
color: Colors.blueGrey.withValues(alpha: 0.5),
thickness: 1,
height: 20,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Column(
spacing: 2,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'(${detailSurah.arti})',
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
Text(
'${detailSurah.tempatTurun?.toUpperCase()} | ${detailSurah.jumlahAyat} Ayat',
),
],
),
StreamBuilder<PlayerState>(
stream: player.playerStateStream,
builder: (context, snapshot) {
final playerState = snapshot.data;
final processingState = playerState?.processingState;
final playing = playerState?.playing ?? false;
if (processingState == ProcessingState.loading ||
processingState == ProcessingState.buffering) {
return IconButton(
icon: CircularProgressIndicator(),
onPressed: null,
);
} else if (playing) {
return AnimatedSwitcher(
duration: Duration(milliseconds: 300),
child: IconButton(
icon: Icon(
Icons.pause_circle_filled,
size: 48,
color: Theme.of(context).colorScheme.secondary,
),
onPressed: player.pause,
),
);
} else {
return AnimatedSwitcher(
duration: Duration(milliseconds: 300),
child: IconButton(
icon: Icon(
Icons.play_circle_filled,
size: 42,
color: Theme.of(context).colorScheme.secondary,
),
onPressed: player.play,
),
);
}
},
),
],
),
StreamBuilder<Duration>(
stream: _positionStream,
builder: (context, positionSnapshot) {
final position = positionSnapshot.data ?? Duration.zero;
return StreamBuilder<Duration?>(
stream: _durationStream,
builder: (context, durationSnapshot) {
final duration = durationSnapshot.data ?? Duration.zero;
return Slider(
value: position.inSeconds.toDouble(),
min: 0,
max: duration.inSeconds.toDouble(),
onChanged:
(value) =>
player.seek(Duration(seconds: value.toInt())),
activeColor: Theme.of(context).colorScheme.secondary,
// Color of the part already played
inactiveColor: Colors.grey[300],
);
},
);
},
),
],
),
),
Expanded(
child: ListView.builder(
itemCount: ayatList.length,
itemBuilder: (context, index) {
return cardAyat(ayatList[index]);
},
),
),
],
),
);
}
Widget cardAyat(Ayat ayat) {
return Card(
elevation: 0.5,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
margin: EdgeInsets.symmetric(vertical: 8, horizontal: 16),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 8,
children: [
Row(
spacing: 4,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Container(
padding: EdgeInsets.all(5),
decoration: BoxDecoration(
border: Border.all(
color: Theme.of(context).colorScheme.primary,
width: 1,
),
borderRadius: BorderRadius.circular(10),
),
child: Text(
'${ayat.surah}:${ayat.nomor}',
style: TextStyle(fontSize: 11),
),
),
Expanded(
child: Directionality(
textDirection: TextDirection.rtl,
child: Text(
ayat.ar ?? '',
style: const TextStyle(fontSize: 24),
),
),
),
],
),
Text('${ayat.idn}'),
],
),
),
);
}
}
Perubahan kode diatas yang pertama memasukkan library audio player,
import 'package:just_audio/just_audio.dart';
Lalu inisisalaisasi libray just_audio
dengan nama kelas AudioPlayer
dan membuat dua varibel untuk menentukan durasi audio, lalu tidak lupa kita inisialisasi player pada iniState
, serta dispose ketika kelas AudioPlayer tidak digunakan.
late AudioPlayer player;
Stream<Duration> get _positionStream => player.positionStream;
Stream<Duration?> get _durationStream => player.durationStream;
@override
void dispose() {
player.dispose();
super.dispose();
}
@override
void initState() {
player = AudioPlayer();
getDetailSurah(widget.id);
super.initState();
}
Kode di atase menggunakan Stream<Duration>
yang artinya stream ini mengalirkan data yang bertipe data duration
, serta untuk mengaksesnya menggunakan get
dimasukkan dalam variabel yang dibuat.
Lalu kita masukkan atau set url untuk audio yang nati kita putar pada fungsi getDetailSurah
pada method result.fold
pada parameter true
atau data benar
void getDetailSurah(int id) async {
final result = await remoteResource.detailSurah(id);
result.fold(
(error) => {
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text('Error: $error'))),
},
(data) {
setState(() {
detailSurah = data;
ayatList.addAll(data.ayat ?? []);
});
// memasukkan url audio
player.setUrl(detailSurah.audio ?? '');
},
);
}
Lalu kita membuat button untuk play audio
// Play Button Audio
StreamBuilder<PlayerState>(
stream: player.playerStateStream,
builder: (context, snapshot) {
final playerState = snapshot.data;
final processingState = playerState?.processingState;
final playing = playerState?.playing ?? false;
if (processingState == ProcessingState.loading ||
processingState == ProcessingState.buffering) {
return IconButton(
icon: CircularProgressIndicator(),
onPressed: null,
);
} else if (playing) {
return AnimatedSwitcher(
duration: Duration(milliseconds: 300),
child: IconButton(
icon: Icon(Icons.pause_circle_filled,
size: 48,
color: Theme.of(context).colorScheme.secondary,
),
onPressed: player.pause,
),
);
} else {
return AnimatedSwitcher(
duration: Duration(milliseconds: 300),
child: IconButton(
icon: Icon(Icons.play_circle_filled,
size: 42,
color: Theme.of(context).colorScheme.secondary,
),
onPressed: player.play,
),
);
}
},
),
Kode di atas kita menggunakan widget StreamBuilder
untuk membuat UI berdasarkan stream data.
player.playerStateStream
ini Stream dari just_audio
yang memancarkan objek PlayerState
— ini mencakup status playing
dan processingState
.
Lalu mengambil data dari snapshot
dari streamBuilder
final playerState = snapshot.data;
final processingState = playerState?.processingState;
final playing = playerState?.playing ?? false;
Lalu membuat loading ketika data di muat.
if (processingState == ProcessingState.loading ||
processingState == ProcessingState.buffering) {
return IconButton(
icon: CircularProgressIndicator(),
onPressed: null,
);
}
lalu jika posisi audio diputar maka menampilkan icon pause.
else if (playing) {
return AnimatedSwitcher(
duration: Duration(milliseconds: 300),
child: IconButton(
icon: Icon(Icons.pause_circle_filled, size: 48),
onPressed: player.pause,
),
);
}
lalu jika tidak memutar lagu akan menampilkan tombol play
else {
return AnimatedSwitcher(
duration: Duration(milliseconds: 300),
child: IconButton(
icon: Icon(Icons.play_circle_filled, size: 42),
onPressed: player.play,
),
);
}
Lalu kita membuat slider untuk mengetahui posisi lagu berapa menit
StreamBuilder<Duration>(
stream: _positionStream,
builder: (context, positionSnapshot) {
final position = positionSnapshot.data ?? Duration.zero;
return StreamBuilder<Duration?>(
stream: _durationStream,
builder: (context, durationSnapshot) {
final duration = durationSnapshot.data ?? Duration.zero;
return Slider(
value: position.inSeconds.toDouble(),
min: 0,
max: duration.inSeconds.toDouble(),
onChanged: (value) =>
player.seek(Duration(seconds: value.toInt())),
activeColor: Theme.of(context).colorScheme.secondary,
// Color of the part already played
inactiveColor: Colors.grey[300],
);
},
);
},
),
Langkah 3 - Membuat Navigation
Setelah membuat tampilan untuk detail surah, sekarang kita akan membuat navigasi yang nanti kita pasang pada halaman home_page.dart
, jadi ketika kita memilih salah satu dari daftar surah nanti kita akan di arahkan ke halaman detail surah.
Kita buka file home_page.dart
lalu kita tambahkan kode berikut:
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DetailSurahPage(id: surah.nomor ?? 0),
),
);
Kode di atas masukkan di bawah comment // Move to detail page
, di dalam widget buildSurahCard()
, sehingga widget tersebut menjadi berikut:
Widget buildSurahCard(SurahModel surah) {
return Container(
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 16),
margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.secondaryContainer,
boxShadow: [
BoxShadow(
color: Colors.blueGrey.withValues(alpha: 0.5),
spreadRadius: 0.5,
offset: Offset(0, 0.5), // changes position of shadow
),
],
borderRadius: BorderRadius.circular(20),
),
child: InkWell(
onTap: () {
// Move to detail page
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DetailSurahPage(id: surah.nomor ?? 0),
),
);
},
child: Row(
spacing: 8,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Stack(
alignment: Alignment.center,
children: [
SvgPicture.asset(
'assets/icons/jewish-star.svg',
colorFilter: ColorFilter.mode(
Theme.of(context).colorScheme.secondary,
BlendMode.srcIn,
),
width: 52,
height: 52,
),
Center(
child: Text(
surah.nomor.toString(),
style: TextStyle(fontWeight: FontWeight.bold),
),
),
],
),
Expanded(
flex: 2,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
surah.namaLatin ?? '',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
Text(surah.arti ?? ''),
],
),
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
'${surah.nama}',
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
),
Text('${surah.jumlahAyat} Ayat'),
],
),
),
],
),
),
);
}
Langkah 4 - Uji Coba
Sekarang kita jalankan aplikasi yang kita buat, lalu pada daftar surah kita pilih salah satu nanti tampilannya kurang lebih seperti berikut.

Lalu untuk detail halaman surah seperti berikut

Coba kita putar atau play audio, jika berhasil maka akan mengeluarkan suara.
Kesimpulan
Jadi pada artikel terakhir ini kita sudah banyak belajar bagaimana membuat halaman detail surah, lalu membuat audio player dengan package just_audio
, serta membuat navigation yang mengarahkan dari daftar list surah ke halaman detail surah. Kurang lebih seperti itu Tutorial Membuat Aplikasi Al-Qur'an dengan Flutter. Terimakasih
Source Code : https://github.com/hunt3r6/flutter-alquran
Membangun Dashboard dan Aplikasi Android Food Store Dengan Laravel Filament, Flutter dan Payment Gateway