Aplikasi Qur'an Digital & Play Audio Menggunakan Flutter

Aplikasi Qur'an Digital & Play Audio Menggunakan Flutter

Pendahuluan

Perpindahan media baca dari media cetak ke media digital telah menjadi 'budaya' yang diminati sejak akses teknologi informasi semakin mudah & cepat didapatkan. Al-Qur'an juga telah mengalami peralihan dengan banyaknya umat muslim menggunakan perangkat telepon genggam mereka sebagai media untuk membaca Al-Qur'an.

Berangkat dari kebiasaan tersebut maka artikel kali ini akan mengangkat case membuat aplikasi Qur'an Digital menggunakan Flutter. Adapun sumber data yang akan digunakan adalah data dari Kemenag yang pastinya sudah kredibel dijadikan sumber rujukan untuk mengambil data digital salinan Al-Qur'an.

Tidak hanya untuk membaca Al-Qur'an, tapi aplikasi yang akan kita buat juga dilengkapi dengan fitur untuk memainkan MP3 (Audio) sehingga pengguna dapat mendengarkan bacaan Al-Qur'an langsung dari aplikasi ini.

Baca Juga: Aplikasi Tracking Resi Ekspedisi Flutter

Install Flutter & Package

Sebelum memulai proses development, install Flutter terlebih dahulu dengan command:

flutter create dw-quran

Buka Flutter yang baru saja di-install, kemudian buka file pubspec.yaml dan modifikasi bagian berikut:

dependencies:
  flutter:
    sdk: flutter

  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^0.1.2
  http: ^0.12.0+2
  provider: ^3.1.0
  audioplayers: ^0.13.2

Penjelasan: 3 package baru tersebut diantaranya: http digunakan untuk request data dari internet, provider untuk state management dan audioplayers untuk memainkan audio dari mp3. Pastikan posisinya sejajar dengan flutter karena posisi sangat berpengaruh.

Ketika fungsi save dijalankan, biasanya secara otomatis akan mengunduh package tersebut. Akan tetapi kita juga bisa melakukannya secara manual menggunakan command:

flutter pub get

Menampilkan List Surah Al-Qur'an

Ada dua screen yang akan digunakan, pertama adalah screen untuk menampilkan list dari seluruh surah yang ada di Al-Qur'an dan screen kedua untuk menampilkan detail atau isi dari surah yang dipilih. Bagian pertama yang akan diselesaikan terlebih dahulu adalah screen untuk menampilkan list surah karena detail surah membutuhkan id surah yang dipilih.

Buka file main.dart dan modifikasi menjadi:

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

import './screen/quran_list.dart';
import './models/quran_model.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        ChangeNotifierProvider.value(
          value: QuranData(), //LOAD PROVIDER QURANDATA UNTUK STATE MANAGEMENT LIST SURAH
        ),
        //PROVIDER LAINNYA AKAN DITEMPAT DISINI, DIPISAHKAN DENGAN KOMA
      ],
      child: MaterialApp(
        title: 'DaengwebID',
        theme: ThemeData(
          primarySwatch: Colors.pink,
        ),
        home: QuranList(), //DEFAULTNYA AKAN ME-LOAD SCREEN QURANLIST
        routes: {
          //ROUTING UNTUK DETAIL SURAH
        },
      ),
    );
  }
}

Perhatikan bagian import statement, ada 2 file yang di-import. File tersebut adalah quran_list.dart dan quran_model.dart. Mari kita selesaikan satu persatu, buat file quran_list.dart di dalam folder lib/screen dan tambahkan code berikut:

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

import '../models/quran_model.dart';
import '../widget/quran_surah.dart';

class QuranList extends StatefulWidget {
  @override
  _QuranListState createState() => _QuranListState();
}

class _QuranListState extends State<QuranList> {
  ScrollController controller; //VARIABLE controller INI BER-TIPE SCROLLCONTROLLER YANG AKAN DIGUNAKAN UNTUK MENDETEKSI EVENT SCROLL PADA LAYAR
  
  //DEFINISIKAN VARIABLE LAINNYA YANG AKAN DIGUNAKAN UNTUK MENAMPILKAN DATA / PROGRESS LOADING
  bool loadMore = false;
  bool firstLoad = true;

  // HOOKS KETIKA CLASS INI DI-RENDER MAKA AKAN MENJALAN FUNGSI YANG DI-APITNYA
  @override
  void initState() {
    //UNTUK MENJALANKAN FUNGSI PROVIDER DIDALAM INITSTATE, MAKA KITA MEMBUTUHKAN PENUNDAAN MENGGUNAKAN FUTURE.DELAYED, DAN DURASINYA KITA SET KE 0
    Future.delayed(Duration.zero).then((_) {
     //LOAD PROVIDER YANG DITAUTKAN DENGAN QURANDATA (BERASAL DARI QURAN_MODEL.DART)
     // KEMUDIAN DIAKHIR KITA JALANKAN FUNGSI getData() YANG NANTINYA AKAN KITA BUAT
      Provider.of<QuranData>(context, listen: false).getData().then((_) {
        //KEMUDIAN SET firstLoad MENJADI FALSE
        setState(() {
          firstLoad = false;
        });
      });
    });
    super.initState();
    controller = ScrollController()..addListener(_scrollListener); //TAMBAHKAN LISTENER SCROLLING
  }

  //KETIKA CLASS INI DITINGGALKAN
  @override
  void dispose() {
    //MAKA HAPUS LISTENER SCROLLING
    controller.removeListener(_scrollListener);
    super.dispose();
  }

  //ADD LISTENER DAN REMOVE LISTENER MASING-MASING MEMANGGIL METHOD _scrollListener
  //KITA DEFINISIKAN METHOD TERSEBUT
  void _scrollListener() {
    //CEK JIKA POSISI = MAX SCROLL (MENTOK PALING BAWAH)
    if (controller.position.pixels == controller.position.maxScrollExtent) {
      //MAKA SET loadMore JADI TRUE
      setState(() {
        loadMore = true;
      });

      //DAN JALANKAN FUNGSI getData() DARI QURAN_MODEL.DART
      Provider.of<QuranData>(context, listen: false).getData().then((_) {
        //JIKA BERHASIL, MAKA SET loadMore JADI FALSE
        setState(() {
          loadMore = false;
        });
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      //BUAT APPBAR UNTUK HEAD APPS
      appBar: AppBar(
        leading: const Icon(Icons.book),
        title: const Text("DaengWeb - Al-Qur'an"),
      ),
      //FLOATING BUTTON KITA GUNAKAN UNTUK MENAMPILKAN PROGRESS LOADING
      floatingActionButton: loadMore ? CircularProgressIndicator() : null,
      body: Container(
        padding: const EdgeInsets.all(5),
        margin: const EdgeInsets.all(5),
        //KETIKA PERTAMA KALI DILOAD MAKA KITA GUNAKAN LOADING DITENGAH LAYAR
        //NAMUN LOAD DATA SELANJUTNYA KITA GUNAKAN LOAD DI POJOK KANAN LAYAR 
        //AGAR TIDAK MENGGANGGU PENGGUNA
        child: firstLoad
            ? Center(
                child: CircularProgressIndicator(),
              )
            //DETEKSI PERUBAHAN DATA MENGGUNAKAN CONSUMER
            //SEHINGGA WIDGET YANG DI-RENDER HANYA WIDGET YANG DIAPIT-NYA
            : Consumer<QuranData>(
                //SETIAP DATANYA KITA BUILD MENGGUNAKAN LISTVIEW AGAR BISA DI SCROLL
                builder: (ctx, data, _) => ListView.builder(
                  controller: controller, //SET CONTROLLER YANG DIBUAT DIAWAL
                  scrollDirection: Axis.vertical,
                  shrinkWrap: true,
                  itemCount: data.items.length, //HITUNG JUMLAH DATA, MAKA LISTVIEW AKAN ME-RNDER SEBANYAK JUMLAH DATA
                  //RENDER DATANYA KE DALAM CUSTOM WIDGET BERNAMA QuranSurah() YANG SELANJUTNYA AKAN KITA BUAT
                  itemBuilder: (ctx, i) => QuranSurah(
                    //KIRIM DATA YANG DIBUTUHKAN
                    data.items[i].id,
                    data.items[i].name,
                    data.items[i].arab,
                    data.items[i].translate,
                    data.items[i].countAyat,
                  ),
                ),
              ),
      ),
    );
  }
}

Penjelasan: setState() digunakan untuk memberitahu bahwa terjadi perubahan data sehingga perlu dilakukan rebuild. Apabila kamu tidak ingin melakukan rebuild widget, maka cukup assign value variable secara langsung tanpa harus diapit oleh setState.

Terdapat kesamaan file yang di-import antara main.dart dan quran_list.dart yakni sama sama meng-import file quran_model.dart, jadi kita akan buat quran_model.dart di dalam folder lib/models dan tambahkan code:

import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';

//CLASS INI AKAN MENG-HANDLE FORMAT DATA YANG DIINGINKAN
class Quran {
  //DEFINISIKAN VARIABLENYA BESERTA TIPE DATANYA
  final int id;
  final String name;
  final String arab;
  final String translate;
  final int countAyat;

  //BUAT CONSTRUCTOR DIMANA KETIKA CLASS INI DI-LOAD MAKA WAJIB MENGIRIMKAN DATA YANG DIMINTA
  Quran({
    @required this.id, 
    @required this.name, 
    @required this.arab, 
    @required this.translate, 
    @required this.countAyat
  });
}

//CLASS INI DIGUNAKAN UNTUK STATE MANAGEMENT PROVIDER
class QuranData with ChangeNotifier {
  //DEFINISIKAN VARIABLE _data DENGAN TIPE LIST DAN FORMAT QURAN DIMANA NILAI DEFAULT ADA EMPTY ARRAY
  List<Quran> _data = []; 
  int offset = 0; //DEFINISIKAN VARIABLE offset DENGAN TIPE int DAN VALUE AWAL ADALAH 0

  //KARENA _data ADALAH PRIVATE PROPERTY DITANDAI DENGAN AWAL _
  //MAKA KITA BUAT GETTER AGAR _data BISA DIAKSES DARI LUAR CLASS
  //DAN GETTERNYA DIBERI NAMA items
  List<Quran> get items {
    return [..._data];
  }

  //FUNGSI getData() DIGUNAKAN UNTUK REQUEST LIST DATA SURAH YANG ADA
  Future<void> getData() async {
    try {
      //REQUEST KE SERVER HANYA DIJALANKAN JIKA OFFSET SAMA DENGAN TOTAL DATA YANG ADA
      if (offset == _data.length) {
        //URL TEMPAT KITA MENGAMBIL DATA SURAH, DIMANA OFFSET NILAINYA TERGANTUNG BERAPA BANYAK DATA YANG SUDAH DI-LOAD
        final url = 'https://quran.kemenag.go.id/index.php/api/v1/surat/${offset}/10';
        //MINTA DATA KE API
        final response = await http.get(url);
        //CONVER DATA NYA MENJADI LIST
        final extractData = json.decode(response.body)['data'] as List;
        //JIKA DATANYA KOSONG
        if (extractData == null || extractData.length == 0) {
          return; //MAKA HENTIKAN FUNGSI
        }
        
        final List<Quran> quranData = []; //BUAT VARIABLE SEMENTARA UNTUK MENAMPUNG DATA
        //LOOPING DATA YANG SUDAH DI CONVERT
        extractData.forEach((value) {
          //TAMBAHKAN DATA TERSEBUT KE DALAM VARIABLE SEMENTARANYA
          //DENGAN FORMAT SESUAI DENGAN CLASS Quran()
          quranData.add(Quran(
            id: value['id'],
            name: value['surat_name'],
            arab: value['surat_text'],
            translate: value['surat_terjemahan'],
            countAyat: value['count_ayat']
          ));
        });
      
        offset += quranData.length; //UBAH VALUE OFFSET DENGAN MENAMBAHKAN DATA BARU
        _data.addAll(quranData); //INSERT DATA BARU KE DALAM VARIABLE _data
        notifyListeners(); //INFORMASIKAN JIKA TERJADI PERUBAHAN
      }
    } catch(error) {
      throw error;
    }
  }
}

Model yang bertugas sebagai state management sudah tersedia, 1 file lagi yang luput adalah file quran_surah.dart dimana file ini import oleh class QuranList(). Buat file baru bernama quran_surah.dart di dalam folder lib/widget dan tambahkan code:

import 'package:flutter/material.dart';

class QuranSurah extends StatelessWidget {
  //DEFINISIKAN VARIABLE APA SAJA YANG DIBUTUHKAN UNTUK INFORMASI SURAH
  final int id;
  final String name;
  final String arab;
  final String translate;
  final int countAyat;

  //BUAT CONSTRUCTOR UNTUK MEMINTA DATA KETIKA CLASS INI DI-LOAD
  QuranSurah(this.id, this.name, this.arab, this.translate, this.countAyat);

  //BUAT METHOD UNTUK REDIRECT KE SCREEN BARU
  //FUNGSI INI NANTINYA DIGUNAKAN UNTUK BERPINDAH KE DETAIL MASING-MASING SURAH
  //DIMANA KETIKA SURAH TERSEBUT DITAP MAKA AKAN PINDAH KE SCREEN DETAIL
  void _viewDetail(BuildContext context) {
    Navigator.of(context).pushNamed('/detail', arguments: id); //MENGGUNAKAN NAVIGATOR DAN MENUJU ROUTES /detail DIMANA ROUTE INI BELUM DIDEFINISIKAN
    //ROUTE DIATAS AKAN DIDEFINISIKAN KEMUDIAN. SELAIN ITU KITA MENGIRIMKAN ID SURAH SEBAGAI ARGUMENT YANG NANTINYA DIGUNAKAN OLEH SCREEN YANG BARU UNTUK MENGAMBIL DATA SURAH TERKAIT
  }

  @override
  Widget build(BuildContext context) {
    return Card(
      elevation: 8,
      margin: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
      child: ListTile(
        //KETIKA LISTTILE DITAP, MAKA AKAN MENJALANKAN METHOD DIATAS
        onTap: () => _viewDetail(context),
        contentPadding: EdgeInsets.symmetric(horizontal: 20, vertical: 10),
        //MULAI DARI SINI HINGGA CODE DIBAWAH HANYA MENAMPILKAN INFORMASI SURAH 
        //LEADING POSISINYA SEBELAH KIRI, DIMANA KITA GUNAKAN UNTUK MENAMPILKAN NOMOR URUT SURAH
        leading: Container(
          padding: EdgeInsets.only(right: 12),
          decoration: BoxDecoration(
            border: Border(
              right: BorderSide(width: 1, color: Colors.black),
            ),
          ),
          child: CircleAvatar(
            child: Text('$id'),
          ),
        ),
        //TITLE POSISINYA DITENGAH, KITA GUNAKAN UNTUK MENAMPILKAN NAMA SURAH (LENGKAP DENGAN BAHASA ARABNYA)
        title: Text(
          '$name ($arab)',
          style: TextStyle(fontWeight: FontWeight.bold),
        ),
        //DAN SUBTITLE POSISINYA DIBAWAH TITLE UNTUK MENAMPILKAN INFORMASI TAMBAHAN
        subtitle: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: <Widget>[
            //PERTAMA ADALAH TERJEMAHAN DARI NAMA SURAH
            Row(
              children: <Widget>[
                Icon(Icons.local_florist, color: Colors.pinkAccent,),
                Expanded(child: Text('Terjemahan: $translate')),
              ],
            ),
            //JUMLAH AYAT DARI SURAH TERSEBUT
            Row(
              children: <Widget>[
                Icon(Icons.local_florist, color: Colors.pinkAccent,),
                Text('Jumlah Ayat: $countAyat'),
              ],
            ),
          ],
        ),
        //POSISINYA PALING KANAN, KITA GUNAKAN UNTUK MENAMPILKAN ICON SAJA
        trailing: Icon(Icons.keyboard_arrow_right, size: 30,),
      ),
    );
  }
}

Sampai pada tahap ini kita sudah berhasil menampilkan list surah dimana fitur yang dimiliki adalah progress loading ketika data sedang di-load dari internet, list view untuk menampilkan data surah, membuat custom widget dimana widget ini re-usable untuk menampilkan informasi surah, menerapkan pagination dengan menampilkan 10 surah per sekali load dan untuk mendeteksi pagination tersebut kita gunakan lazy load atau dengan kata lain mendeteksi scroll screen.

Quran Detail & Play Audio

Masih ingat dengan penjelasan sebelumnya? Jadi ketika ListTile di-tap, maka akan diarahkan ke screen yang akan kita buat pada pembahasan kali ini. Kembali ke main.dart, buka file tersebut dan tambahkan code berikut pada bagian import statement:

import './models/quran_ayat_model.dart';
import './screen/quran_detail.dart';

Masih dengan file yang sama, pada bagian array providers dari MultiProvider(), tambahkan code berikut untuk meng-handle model QuranAyat()

ChangeNotifierProvider.value(
  value: QuranAyat(),
)

Perubahan terakhir dari main.dart adalah pada bagian routes, modifikasi menjadi:

routes: {
  '/detail': (ctx) => QuranDetail(),
},

Kita tinggalkan main.dart, maka tugas selanjutnya adalah membuat class baru bernama quran_detail.dart, tempatkan file tersebut di dalam folder lib/screen dan tambahkan code:

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:audioplayers/audioplayers.dart';

import '../models/quran_model.dart'; //IMPORT QURAN MODEL UNTUK MENGAMBIL DATA SURAH
import '../models/quran_ayat_model.dart'; //IMPORT MODEL BARU YANG NANTINYA AKAN KITA BUAT

import '../widget/quran_read.dart'; //CUSTOM WIDGET UNTUK MENAMPILKAN ISI SURAH

class QuranDetail extends StatefulWidget {
  @override
  _QuranDetailState createState() => _QuranDetailState();
}

class _QuranDetailState extends State<QuranDetail> {
  //VARIABLE INI UNTUK MENG-HANDLE BOTTOM NAVIGATION (PREVIOUS = 0, PLAY = 1 DAN NEXT = 2)
  //VALUE DEFAULTNYA KITA SET 2 (NEXT) 
  int bottomIndex = 2; 
  int offset = 0; //OFFSET UNTUK MENANDAI BERAPA DATA YANG SUDAH DI-LOAD
  int totalData = 0; //UNTUK MENYIMPAN INFORMASI ID DATA TERAKHIR YANG DI-LOAD
  int id; //ID SURAH
  bool isPlay = false; //VARIABLE UNTUK PLAY/STOP AUDIO
  AudioPlayer audioPlayer = AudioPlayer(); //VARIABLE YANG AKAN MENG-HANDLE AUDIO

  //METHOD UNTUK MENJALANKAN AUDIO DARI SURAH YANG DIPILIH
  void play() async {
    //JIKA isPlay = false
    if (!isPlay) {
      //MAKA KITA AMBIL URL DARI SURAH TERKAIT MENGGUNAKAN METHOD findMp3Url DENGAN MENGIRIMKAN ID SURAH
      final mp3URL = Provider.of<QuranAyat>(context, listen: false).findMp3Url(id);
      int result = await audioPlayer.play(mp3URL); //PLAY AUDIONYA
      if (result == 1) {
        //JIKA BERHASIL DIPLAY
        setState(() {
          isPlay = true; //MAKA SET VARIABLE NYA JADI TRUE
        });
      }
    } else {
        //JIKA ISPLAY = TRUE MAKA KITA JALANKAN FUNGSI STOP AUDIO
        int result = await audioPlayer.stop();
        if (result == 1) {
          setState(() {
            isPlay = false; //DAN SET isPlay JADI FALSE
          });
        }
    }
  }

  //KETIKA BOTTOM NAVIGATION DI-TAP MAKA AKAN MENJALANKAN FUNGSI INI
  void _changeBottomIndex(index) {
    //DI-CEK INDEX YANG DI-TAP, JIKA VALUE-NYA 1 (TOMBOL PLAY)
    if (index == 1) {
      play(); //MAKA KITA JALANKAN METHOD play()
    }

    //AMBIL DATA TERBARU YANG ADA DI CLASS QuranAyat DENGAN PROPERTY items
    final loadData = Provider.of<QuranAyat>(context, listen: false).items;
    //KEMUDIAN KITA AMBIL NOMOR SURAH DARI DATA YANG TERAKHIR
    totalData = loadData.length > 0 ? loadData[loadData.length - 1].ayatNumber:0;
    
    //JIKA INDEX 0 (PREVIOUS)
    if (index == 0) {
      //OFFSET KITA KURANGI KARENA AKAN KEMBALI KE DATA SEBELUMNYA
      offset -= totalData == offset ? (loadData.length * 2):loadData.length;
    } else if (index == 2) {
        //JIKA INDEX 2 (NEXT), MAKA OFFSET KITA TAMBAH KARENA AKAN KE DATA BERIKUTNYA
        offset += totalData == offset ? (loadData.length * 2):loadData.length;
    }

    setState(() {
      bottomIndex = index; //SET STATE UNTUK MEMBERITAHU BAHWA ADA PERUBAHAN MAKA WIDGET AKAN DIRENDER KEMBALI
    });
  }

  @override
  Widget build(BuildContext context) {
    id = ModalRoute.of(context).settings.arguments as int; //DAPATKAN ARGUMEN YANG DIKIRIMKAN DARI PAGE SEBELUMNYA
    //KEMUDIAN CARI DATA BERDASARKAN ID SURAH YANG DITERIMA
    final data = Provider.of<QuranData>(context, listen: false).findById(id);

    return Scaffold(
        appBar: AppBar(
          title: Column(
            crossAxisAlignment: CrossAxisAlignment.center,
            children: <Widget>[
              Text("DaengWeb Al-Qur'an"),
              Text(
                '${data.name} - ${data.arab}',
                style: TextStyle(fontSize: 14, color: Colors.white70),
              ),
            ],
          ),
        ),
        body: Container(
          padding: const EdgeInsets.all(5),
          margin: const EdgeInsets.all(5),
          //PERHATIKAN BAGIAN SEBELUMNYA, KITA TIDAK MENGGUNAKAN FUNGSI getDetail()
          //KARENA FUTURE BUILDER SECARA OTOMATIS AKAN BERJALAN KETIKA SCREEN DI-LOAD
          //DAN KETIKA USER TAP NEXT / PREVIOUS, KITA MENGGUNAKAN SET STATE YANG BERARTI WIDGET DI RE-RENDER, MAKA FUTURE BUILDER AKAN DIJALANKAN KEMBALI
          child: FutureBuilder(
            future:
                Provider.of<QuranAyat>(context, listen: false).getDetail(id, bottomIndex, offset, totalData), //SECARA OTOMATIS FUNGSI INI AKAN DIJALANKAN SETIAP KALI WIDGET BERUBAH UNTUK MENGAMBIL DATA BERDASARKAN ID SURAH, INDEX BOTTOM NAVIGATION DAN OFFSET YANG DI-REQUEST
            builder: (context, snapshot) {
              //KETIKA PROSES REQUEST BERLANGSUNG
              if (snapshot.connectionState == ConnectionState.waiting) {
                //MAKA PROGRESS LOADING DIJALANKAN
                return Center(
                  child: CircularProgressIndicator(),
                );
              } else {
                //JIKA TERJADI ERROR
                if (snapshot.hasError) {
                  //MAKA INFORMASI ERROR DI RENDER
                  return Center(
                    child: Text("Error! Periksa Koneksi Anda"),
                  );
                } else {
                  //SELAIN ITU MAKA KITA RENDER ISI SURAH TERKAIT
                  return Consumer<QuranAyat>(
                    //MENGGUNAKAN CONSUMER UNTUK MENGAMBIL DATA STATE DAN LIST VIEW UNTUK RENDER CONTENT-NYA
                    builder: (ctx, data, _) => ListView.builder(
                      shrinkWrap: true,
                      itemCount: data.items.length, //LIST VIEW AKAN DIJALANKAN BERDASARKAN JUMLAH DATA
                      //LAGI LAGI KITA GUNAKAN CUSTOM WIDGET AGAR CODINGAN DIFILE INI TIDAK PANJANG, CLASS QuranRead() AKAN DIBUAT SELANJUTNYA.
                      itemBuilder: (ctx, i) => QuranRead(
                        data.items[i].ayatNumber,
                        data.items[i].ayatArab,
                        data.items[i].ayatText,
                      ),
                    ),
                  );
                }
              }
            },
          ),
        ),
        //BOTTOM NAVIGATION NYA KITA BUAT DISINI
        bottomNavigationBar: BottomNavigationBar(
          //DIMANA INDEXNYA SESUAI DENGAN BOTTOMINDEX
          currentIndex: bottomIndex,
          //DAN ITEMSNYA ADALAH 3, PREVIOUS, PLAY DAN NEXT
          items: [
            BottomNavigationBarItem(
                icon: Icon(Icons.arrow_left), title: Text('Previous')),
            BottomNavigationBarItem(
                icon: Icon(isPlay ? Icons.stop:Icons.play_arrow), title: Text('${!isPlay ? "Play":"Stop"}')),
            BottomNavigationBarItem(
                icon: Icon(Icons.arrow_right), title: Text('Next')),
          ],
          //KETIAK DI-TAP MAKA AKAN MENJALANKAN FUNGSI _changeBottomIndex
          onTap: _changeBottomIndex,
        ));
  }
}

Sebelum membuat file selanjutnya, lengkapi terlebih dahulu fungsi dari file yang ada. Jadi dari codingan diatas ada fungsi untuk menjalankan method findById() dimana method ini bertujuan untuk mengambil informasi surah berdasarkan ID-nya. Buka file quran_model.dart dan tambahkan method berikut di dalam class QuranData()

Quran findById(int id) {
  return _data.firstWhere((item) => item.id == id); //GET DATA SURAH BERDASARKAN ID
}

Dua langkah terakhir adalah membuat file quran_read.dart dan quran_ayat_model.dart. Pertama kita buat file quran_ayat_model.dart dan letakkan di dalam folder lib/widget, kemudian tambahkan code:

import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';

//CLASS INI SAMA DENGAN CLASS SEBELUMNYA, UNTUK MENG-HANDLE FORMAT DATA YANG DIINGINKAN
class QuranAyatModel {
  final int ayatId;
  final int ayatNumber;
  final int surahId;
  final int juzId;
  final String ayatArab;
  final String ayatText;

  QuranAyatModel({
    @required this.ayatId,
    @required this.ayatNumber,
    @required this.surahId,
    @required this.juzId,
    @required this.ayatArab,
    @required this.ayatText,
  });
}

//CLASS INI UNTUK MENG-HANDLE STATE MANAGEMENT
class QuranAyat with ChangeNotifier {
  List<QuranAyatModel> _data = []; //DATA CONTENT SURAH KITA BUAT BERTIPE LIST DENGAN FORMAT SESUAI DENGAN CLASS QuranAyatModel
  
  //KARENA URL MP3 TIDAK ADA DALAM API, MAKA KITA BUAT MANUAL. DIMANA URUTANNYA KITA SESUAIKAN DENGAN URUTAN SURAH
  //DATA INI MASIH BELUM LENGKAP, SILAHKAN DILENGKAPI
  List _mp3 = [
    'http://ia802609.us.archive.org/13/items/quraninindonesia/001AlFaatihah.mp3',
    'http://ia802609.us.archive.org/13/items/quraninindonesia/002AlBaqarah.mp3',
    'http://ia802609.us.archive.org/13/items/quraninindonesia/003AliImran.mp3',
    'http://ia802609.us.archive.org/13/items/quraninindonesia/004AnNisaa.mp3',
    'http://ia802609.us.archive.org/13/items/quraninindonesia/005AlMaaidah.mp3',
    'http://ia802609.us.archive.org/13/items/quraninindonesia/006AlAnaam.mp3',
    'http://ia802609.us.archive.org/13/items/quraninindonesia/007AlAaraaf.mp3',
    'http://ia802609.us.archive.org/13/items/quraninindonesia/008AlAnfaal.mp3',
    'http://ia802609.us.archive.org/13/items/quraninindonesia/009AtTaubah.mp3',
    'http://ia802609.us.archive.org/13/items/quraninindonesia/010Yunus.mp3',
    'http://ia802609.us.archive.org/13/items/quraninindonesia/011Huud.mp3',
    'http://ia802609.us.archive.org/13/items/quraninindonesia/012Yusuf.mp3',
    'http://ia802609.us.archive.org/13/items/quraninindonesia/013ArRaad.mp3',
    'http://ia802609.us.archive.org/13/items/quraninindonesia/014Ibrahim.mp3',
    'http://ia802609.us.archive.org/13/items/quraninindonesia/015AlHijr.mp3',
    'http://ia802609.us.archive.org/13/items/quraninindonesia/016AnNahl.mp3',
    'http://ia802609.us.archive.org/13/items/quraninindonesia/017AlIsraa.mp3',
    'http://ia802609.us.archive.org/13/items/quraninindonesia/018AlKahfi.mp3',
    'http://ia802609.us.archive.org/13/items/quraninindonesia/019Maryam.mp3',
    'http://ia802609.us.archive.org/13/items/quraninindonesia/020Thaahaa2.mp3'
  ];

  //GETTER AGAR VALUE _data BISA DIAKSES DARI LUAR CLASS
  List<QuranAyatModel> get items {
    return [..._data];
  }

  //MENGAMBIL URL MP3 BERDASARKAN ID SURAH
  String findMp3Url(id) {
    return _mp3[id - 1]; //KARENA ID SURAH DIMULAI DARI 1 SEDANGKAN ARRAY DIMULAI DARI 0, MAKA KITA KURANGI 1 SETIAP ID SURAHNYA
  }

  //METHOD UNTUK MENGAMBIL ISI SURAH BERDASARKAN ID SURAH
  Future<void> getDetail(int id, int navigationBarIndex, int offset, int total) async {
    //PROSES REQUEST HANYA DIJALANKAN JIKA MEMENUHI KONDISI YANG ADA DI DALAM IF
    //HAL INI DILAKUKAN KARENA SETSTATE JUGA BERARTI AKAN MENJALANKAN FUNGSI INI KETIKA TOMBOL PLAY DITAP
    //AKAN TETAP KITA TIDAK INGIN MENJALANKAN FUNGSI INI JIKA BUKAN TOMBOL NEXT/PREVIOUS
    if ((navigationBarIndex == 2 && total == offset) || (navigationBarIndex == 0 && total > offset)) {
      //GET DATA BERDASARKAN ID DAN OFFSET, DIMANA PER SEKALI LOAD KITA AMBIL 10 DATA
      final url = 'https://quran.kemenag.go.id/index.php/api/v1/ayatweb/$id/0/$offset/10';
      final response = await http.get(url);
      final extractData = json.decode(response.body)['data'] as List; //FORMAT DATA BERBENTUK LIST

      if (extractData == null) {
        return;
      }

      final List<QuranAyatModel> ayatData = [];
      //LOOPING DATA
      extractData.forEach((value) {
        //DAN MASUKKAN DATANYA SESUAI FORMAT QuranAyatModel
        ayatData.add(QuranAyatModel(
          ayatId: value['aya_id'],
          ayatNumber: value['aya_number'],
          surahId: value['sura_id'], 
          juzId: value['juz_id'],
          ayatArab: value['aya_text'],
          ayatText: value['translation_aya_text']
        ));
      });

      _data = ayatData; //TAMBAHKAN DATANYA KE DALAM _data
      notifyListeners();
    }
  }
}

Bagian terakhir adalah membuat file quran_read.dart dan tempatkan di dalam folder lib/widget, kemudian tambahkan code:

import 'package:flutter/material.dart';

class QuranRead extends StatelessWidget {
  //DEFINISIKAN VARIABLE DARI INFORMAIS YANG DIBUTUHKAN
  final int ayatNumber;
  final String ayatArab;
  final String ayatText;

  //BUAT CONSTRUCTOR UNTUK MEMINTA DATA
  QuranRead(this.ayatNumber, this.ayatArab, this.ayatText);

  @override
  Widget build(BuildContext context) {
    return Card(
      elevation: 8,
      child: ListTile(
        contentPadding: EdgeInsets.symmetric(horizontal: 20, vertical: 10),
        leading: CircleAvatar(child: Text('$ayatNumber'),), //POSISI KIRI KITA TAMPILKAN NOMOR AYAT
        //DITENGAH KITA TAMPILKAN TEXT ARABNYA
        title: Text(
          '$ayatArab',
          textAlign: TextAlign.right,
          style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
        ),
        //DIBAWAH TITLE TAMPILKAN TERJEMAHANNYA
        subtitle: Text('$ayatText'),
      ),
    );
  }
}

Kesimpulan

Studi kasus sederhana ini telah membantu kita dalam belajar Flutter, dimana sepanjang artikel ini kita telah belajar bagimana mengambil data dari internet, menampilkan list ayat dan surah menggunakan ListView, membuat pagination, memainkan audio, membuat progress loading, membuat bottom navigation dan berpindah screen menggunakan navigator.

Adapun dokumentasi code dari artikel ini bisa kamu lihat di Github.

Category:
Share:

Comments