VIP·LEGEND / BAB 10
01 / 21
KELAS 11 · SEMESTER 2 · FLUTTER PRODUCTION
📱DEVICE
📷CAMERA
🔔NOTIFY
📄PDF
🔗DEEPLINK
📤SHARE

BAB 10
ADVANCED MOBILE FEATURES

Naik kelas dari aplikasi biasa menjadi aplikasi kelas produksi — mengakses kamera, file, notifikasi, dan dunia di luar app-mu.

📷 Camera 🖼️ Gallery 📁 File Picker 📄 PDF 📤 Share 🔔 Notification 🔗 Deep Link 🚀 URL Launcher

Gunakan / untuk navigasi, untuk daftar slide. Setiap slide bisa di-scroll pada zoom 100%.

ORIENTASI

Peta Perjalanan BAB 10

10 fitur device-level + 1 mini project. Semua bermuara pada satu aplikasi nyata: Productivity App.

ANALOGI BERGERAK
🧑‍💻
🛠️
🏭
PENJELASAN

Bayangkan kamu pindah dari bengkel rumahan (app sederhana BAB 1–9) ke pabrik perakitan (app produksi). Tukang (🛠️) terus membawa komponen baru — kamera, notifikasi, PDF — dari developer ke produk akhir. Itulah yang kita rakit di bab ini.

🗺️ 10 Stasiun Fitur

📷

1. Camera

Ambil foto langsung dari kamera native device.

🖼️

2. Gallery

Pilih foto/video dari galeri perangkat.

📁

3. File Picker

Pilih file apa pun dari storage.

📄

4. PDF Viewer

Tampilkan dokumen PDF di dalam app.

🖨️

5. PDF Generator

Buat PDF dari data app.

📤

6. Share

Bagikan konten ke app lain.

🔔

7. Push Notification

Notifikasi dari server (FCM).

8. Local Notification

Notifikasi lokal & terjadwal.

🔗

9. Deep Link

Buka halaman spesifik via URL.

🚀

10. URL Launcher

Buka browser, email, telepon.

🏆

11. Mini Project

Productivity App lengkap.

3 Tugas

Latihan akhir bab.

🎯 Tujuan Belajar: Setelah bab ini kamu mampu mengintegrasikan fitur device-level ke aplikasi Flutter dengan permission yang benar, UX yang baik, dan arsitektur bersih.
FONDASI

Setup Project & Izin (Permission)

POINT MATERI

Fitur device-level butuh izin. Tanpa izin yang dideklarasikan, app akan crash atau diam-diam gagal. Kita pasang dependency & permission lebih dulu.

ANALOGI BERGERAK
🧳
🛂
🚪
PENJELASAN ANALOGI

Permission seperti petugas imigrasi (🛂) di bandara. Sebelum kamu (🧳 app) masuk ke "negara" kamera atau storage, kamu harus menunjukkan visa (izin di manifest). Tidak ada visa → ditolak masuk.

CODE — pubspec.yaml
pubspec.yaml
dependencies:
  flutter:
    sdk: flutter
  image_picker: ^1.1.2        # camera + gallery
  file_picker: ^8.1.2         # pilih file
  flutter_pdfview: ^1.3.2     # tampilkan PDF
  pdf: ^3.11.1                # generate PDF
  printing: ^5.13.2           # print / preview PDF
  share_plus: ^10.1.2         # share ke app lain
  firebase_messaging: ^15.1.3 # push notification
  flutter_local_notifications: ^17.2.3
  app_links: ^6.3.2           # deep link
  url_launcher: ^6.3.1        # buka URL/telepon/email
  path_provider: ^2.1.4       # akses folder lokal
CODE — AndroidManifest.xml & Info.plist
AndroidManifest.xml
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission
  android:name="android.permission.POST_NOTIFICATIONS"/>
<uses-permission
  android:name="android.permission.READ_MEDIA_IMAGES"/>
ios/Runner/Info.plist
<key>NSCameraUsageDescription</key>
<string>App butuh kamera untuk foto</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>App butuh akses galeri</string>
DEMO — instalasi
# pasang semua dependency $ flutter pub get # jalankan di device asli (fitur device-level tidak penuh di web) $ flutter run
⚠️ Penting: Sejak Android 13, izin POST_NOTIFICATIONS & READ_MEDIA_IMAGES wajib dideklarasikan, dan harus diminta saat runtime.
FITUR 01

📷 Camera — Akses Kamera Native

POINT MATERI

Dengan image_picker, kita memanggil aplikasi kamera bawaan device, lalu menerima kembali file foto untuk ditampilkan/diproses.

ANALOGI BERGERAK
📸
🧑
PENJELASAN ANALOGI

Bayangkan fotografer studio. Kamu (app) menekan tombol, lampu flash menyala (kilatan), dan fotografer menyerahkan hasil cetak ke tanganmu. App tidak perlu tahu cara kerja lensa — cukup minta dan terima hasil. Itu inti image_picker.

CODE — Flutter
camera_service.dart
import 'dart:io';
import 'package:image_picker/image_picker.dart';

class CameraService {
  final ImagePicker _picker = ImagePicker();

  /// Buka kamera, kembalikan File foto (null jika dibatalkan)
  Future<File?> takePhoto() async {
    final XFile? shot = await _picker.pickImage(
      source: ImageSource.camera,
      imageQuality: 80,        // kompres 80%
      maxWidth: 1280,
    );
    if (shot == null) return null; // user batal
    return File(shot.path);
  }
}
widget pemakaian
ElevatedButton.icon(
  icon: const Icon(Icons.camera_alt),
  label: const Text('Ambil Foto'),
  onPressed: () async {
    final file = await CameraService().takePhoto();
    if (file != null) setState(() => _photo = file);
  },
);
// menampilkan: _photo != null ? Image.file(_photo!) : Placeholder()
DEMO VISUAL
📷 Camera Demo
📸
✅ Foto tersimpan (1280px, 80%)
💡 Tips: selalu set imageQuality & maxWidth agar ukuran file tidak membengkak saat di-upload ke server.
FITUR 02

🖼️ Gallery — Pilih dari Galeri

POINT MATERI

Sama seperti kamera, tapi sumbernya ImageSource.gallery. Bisa juga memilih banyak foto sekaligus dengan pickMultiImage().

ANALOGI BERGERAK
🗂️
🖼️
🖼️
PENJELASAN ANALOGI

Galeri adalah album foto keluarga (🗂️). Kamu membuka album, lalu mencabut beberapa lembar foto untuk dibawa. App hanya menerima salinan foto terpilih, bukan seluruh album.

CODE — Flutter
gallery_service.dart
final ImagePicker _picker = ImagePicker();

// satu foto
Future<File?> pickOne() async {
  final x = await _picker.pickImage(source: ImageSource.gallery);
  return x == null ? null : File(x.path);
}

// banyak foto sekaligus
Future<List<File>> pickMany() async {
  final list = await _picker.pickMultiImage(imageQuality: 80);
  return list.map((x) => File(x.path)).toList();
}
DEMO VISUAL
🖼️ Gallery Demo
✅ 3 foto dipilih
💡 image_picker menangani kamera & galeri sekaligus — cukup ganti parameter source.
FITUR 03

📁 File Picker — Pilih File Apa Pun

POINT MATERI

Paket file_picker membuka file manager device untuk memilih PDF, dokumen, audio, atau tipe khusus (filter berdasarkan ekstensi).

ANALOGI BERGERAK
🗄️
📄
📊
🎵
PENJELASAN ANALOGI

Bayangkan lemari arsip kantor (🗄️) berisi ribuan dokumen. File picker adalah asisten yang membuka lemari, kamu sebutkan "ambil yang PDF saja", dan ia mengambilkan dokumen sesuai filter.

CODE — Flutter
file_service.dart
import 'package:file_picker/file_picker.dart';

Future<PlatformFile?> pickDocument() async {
  final result = await FilePicker.platform.pickFiles(
    type: FileType.custom,
    allowedExtensions: ['pdf', 'docx', 'xlsx'],
    allowMultiple: false,
  );
  if (result == null) return null;        // dibatalkan
  final file = result.files.single;
  print('Nama: ${file.name}, ukuran: ${file.size} bytes');
  return file;                            // file.path -> lokasi file
}
DEMO VISUAL
📁 File Picker
📄
laporan_2026.pdf
240 KB · PDF
✅ laporan_2026.pdf dipilih
⚠️ Di Android, gunakan withData: true bila ingin membaca byte file langsung tanpa path (berguna untuk upload).
FITUR 04

📄 PDF Viewer — Tampilkan Dokumen

POINT MATERI

flutter_pdfview me-render PDF dari path lokal langsung di dalam app, lengkap dengan navigasi halaman.

ANALOGI BERGERAK
📖
PENJELASAN ANALOGI

PDF Viewer adalah meja baca perpustakaan. Buku (file PDF) dibuka di hadapanmu, kamu bisa membalik halaman, zoom, tapi tidak bisa mengubah isinya — hanya membaca.

CODE — Flutter
pdf_view_page.dart
import 'package:flutter_pdfview/flutter_pdfview.dart';

class PdfViewPage extends StatelessWidget {
  final String path;             // path file PDF lokal
  const PdfViewPage({super.key, required this.path});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Baca PDF')),
      body: PDFView(
        filePath: path,
        swipeHorizontal: true,
        onPageChanged: (page, total) =>
            debugPrint('Halaman ${page! + 1} dari $total'),
      ),
    );
  }
}
DEMO VISUAL
📄 Baca PDF
LAPORAN PRODUKTIVITAS
▪ Tugas selesai: 24
▪ Tugas tertunda: 3
▪ Fokus harian: 4.2 jam

◀ Halaman 1 / 3 ▶

FITUR 05

🖨️ PDF Generator — Buat Dokumen

POINT MATERI

Paket pdf + printing membangun dokumen PDF dari data app (teks, tabel, gambar) lalu menyimpan/print/preview.

ANALOGI BERGERAK
⌨️
📄
🖨️
PENJELASAN ANALOGI

Ini kebalikan dari Viewer. Di sini app adalah mesin percetakan. Data mentah (⌨️) masuk, dirakit menjadi lembar dokumen (📄), lalu keluar sebagai hasil cetak (🖨️) yang siap dibagikan.

CODE — Flutter
pdf_generator.dart
import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart' as pw;
import 'package:printing/printing.dart';

Future<void> buildReport(List<String> tasks) async {
  final doc = pw.Document();
  doc.addPage(pw.Page(
    pageFormat: PdfPageFormat.a4,
    build: (ctx) => pw.Column(
      crossAxisAlignment: pw.CrossAxisAlignment.start,
      children: [
        pw.Text('Laporan Tugas',
            style: pw.TextStyle(fontSize: 24, fontWeight: pw.FontWeight.bold)),
        pw.SizedBox(height: 16),
        ...tasks.map((t) => pw.Bullet(text: t)),
      ],
    ),
  ));
  // preview + opsi print/share bawaan OS
  await Printing.layoutPdf(onLayout: (format) async => doc.save());
}
DEMO — alur generate (FLOWCHART ANIMASI)
🗃️Data App
🧱pw.Document
📄doc.save()
🖨️Print / Share
💡 Printing.layoutPdf otomatis memunculkan dialog print/share OS — tidak perlu UI sendiri.
FITUR 06

📤 Share — Berbagi ke App Lain

POINT MATERI

share_plus memunculkan share sheet OS untuk membagikan teks, link, atau file ke WhatsApp, Email, dan app lain.

ANALOGI BERGERAK
📦
📤
💬
📧
PENJELASAN ANALOGI

Share seperti kantor pos / kurir (📤). Kamu menyerahkan paket (konten), kurir menanyakan "mau dikirim ke mana?" lalu mengantarnya ke tujuan (WhatsApp 💬, Email 📧). App-mu tidak perlu tahu detail tiap tujuan.

CODE — Flutter
share_service.dart
import 'package:share_plus/share_plus.dart';

// berbagi teks / link
Future<void> shareText(String msg) =>
    Share.share(msg, subject: 'Dari Productivity App');

// berbagi file (mis. PDF hasil generate)
Future<void> shareFile(String path) async {
  await Share.shareXFiles(
    [XFile(path)],
    text: 'Ini laporan tugasku 📄',
  );
}
DEMO VISUAL — Share Sheet
📤 Share Demo

Bagikan via…

💬
WhatsApp
📧
Email
📁
Drive
FITUR 07

🔔 Push Notification (FCM)

POINT MATERI

Notifikasi push dikirim dari server (Firebase Cloud Messaging) ke device, bahkan saat app tertutup. Setiap device punya FCM token sebagai alamat.

ANALOGI BERGERAK
☁️
✉️
🔔
PENJELASAN ANALOGI

Server (☁️) seperti kantor pusat yang mengirim surat (✉️) ke alamat rumahmu (FCM token). Begitu surat tiba, bel pintu berbunyi (🔔) — itulah notifikasi yang muncul di layar, walau kamu sedang tidak membuka app.

CODE — alur data FCM
push_service.dart
import 'package:firebase_messaging/firebase_messaging.dart';

class PushService {
  final _fcm = FirebaseMessaging.instance;

  Future<void> init() async {
    // 1. minta izin (Android 13+ / iOS)
    await _fcm.requestPermission();
    // 2. ambil token device (kirim ke server-mu)
    final token = await _fcm.getToken();
    print('FCM token: $token');
    // 3. pesan saat app dibuka (foreground)
    FirebaseMessaging.onMessage.listen((msg) {
      print('Pesan masuk: ${msg.notification?.title}');
    });
    // 4. user tap notifikasi -> app dibuka
    FirebaseMessaging.onMessageOpenedApp.listen((msg) {
      // navigasi ke halaman terkait
    });
  }
}
DEMO — alur push (FLOWCHART ANIMASI)
🖥️Server
☁️FCM
📱Device
🔔Tampil
FITUR 08

⏰ Local Notification — Terjadwal

POINT MATERI

Berbeda dari push, local notification dibuat oleh device itu sendiri — tanpa server. Cocok untuk pengingat & alarm terjadwal.

ANALOGI BERGERAK
🔔
PENJELASAN ANALOGI

Local notification adalah jam weker (⏰) di kamarmu. Kamu menyetelnya sendiri untuk berbunyi jam 7. Tidak ada kantor pos, tidak ada internet — device menyimpan jadwal dan membunyikan bel (🔔) saat waktunya tiba.

CODE — Flutter
local_notif.dart
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:timezone/timezone.dart' as tz;

final plugin = FlutterLocalNotificationsPlugin();

Future<void> scheduleReminder(DateTime when, String title) async {
  await plugin.zonedSchedule(
    0,                                   // id notifikasi
    title,
    'Saatnya mengerjakan tugasmu!',
    tz.TZDateTime.from(when, tz.local),
    const NotificationDetails(
      android: AndroidNotificationDetails(
        'reminder_channel', 'Pengingat Tugas',
        importance: Importance.max, priority: Priority.high,
      ),
    ),
    androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle,
  );
}
DEMO VISUAL — Notifikasi muncul
⏰ 07:00 · Senin
🔔
Pengingat Tugas

Saatnya mengerjakan tugasmu!

📚
Belajar Flutter

Sesi fokus 25 menit dimulai.

💡 Push vs Local: Push = dari server (butuh internet). Local = dari device (jalan offline, ideal untuk reminder).
FITUR 09

🔗 Deep Link — Lompat ke Halaman

POINT MATERI

Deep link membuka halaman spesifik di dalam app dari URL eksternal, misal myapp://task/42 langsung membuka detail tugas #42.

ANALOGI BERGERAK
🔗
🏃
🚪
PENJELASAN ANALOGI

URL biasa membawamu ke pintu depan gedung (home), lalu kamu harus jalan kaki ke ruangan tujuan. Deep link seperti lift ekspres (🏃) yang langsung mengantarmu ke pintu ruangan spesifik (🚪) — lewati lobi.

CODE — Flutter (app_links)
deeplink_service.dart
import 'package:app_links/app_links.dart';

final _appLinks = AppLinks();

Future<void> initDeepLinks(GlobalKey<NavigatorState> navKey) async {
  // saat app dibuka dari kondisi mati
  final initial = await _appLinks.getInitialLink();
  if (initial != null) _handle(initial, navKey);

  // saat app sedang berjalan
  _appLinks.uriLinkStream.listen((uri) => _handle(uri, navKey));
}

void _handle(Uri uri, GlobalKey<NavigatorState> navKey) {
  // myapp://task/42  -> pathSegments = ['task','42']
  if (uri.pathSegments.first == 'task') {
    final id = uri.pathSegments[1];
    navKey.currentState?.pushNamed('/task', arguments: id);
  }
}
DEMO — alur deep link (FLOWCHART ANIMASI)
🔗myapp://task/42
⚙️app_links parse
🚪Detail Task #42
FITUR 10

🚀 URL Launcher — Keluar dari App

POINT MATERI

url_launcher membuka resource di luar app: browser (https), email (mailto), telepon (tel), WhatsApp, atau peta.

ANALOGI BERGERAK
🚀
🌐
📞
PENJELASAN ANALOGI

URL Launcher adalah peluncur roket (🚀). App-mu menyiapkan "tujuan" (https / tel / mailto) lalu menembakkannya ke luar angkasa — sistem operasi menangkap dan membuka aplikasi yang tepat (browser 🌐, dialer 📞).

CODE — Flutter
launcher_service.dart
import 'package:url_launcher/url_launcher.dart';

Future<void> openUrl(String url) async {
  final uri = Uri.parse(url);
  if (await canLaunchUrl(uri)) {
    await launchUrl(uri, mode: LaunchMode.externalApplication);
  }
}

// contoh pemakaian
openUrl('https://flutter.dev');     // browser
openUrl('tel:+628123456789');       // panggil
openUrl('mailto:guru@sekolah.id');  // email
openUrl('https://wa.me/628123456'); // WhatsApp
DEMO VISUAL
🚀 URL Launcher
🚀 Membuka aplikasi eksternal…
⚠️ Android 11+: deklarasikan <queries> di manifest agar canLaunchUrl mengenali scheme tel/mailto.
BEST PRACTICE

🛡️ Pola Permission Runtime

POINT MATERI

Mendeklarasikan izin di manifest belum cukup. Sejak Android 6+/iOS, izin sensitif harus diminta saat runtime dengan UX yang baik.

ANALOGI BERGERAK
🧑
🛂
📋
📷
PENJELASAN ANALOGI

Seperti masuk ruang server perusahaan: punya kartu akses (manifest) saja tidak cukup, satpam (🛂) tetap meminta konfirmasi langsung di pintu. Kamu jelaskan tujuanmu (rationale), baru pintu kamera (📷) terbuka.

FLOWCHART ANIMASI — State Permission
▶️User tekan "Ambil Foto"
Status izin?
🛂granted → buka kamera
denied → tampilkan rationale
permanent → arahkan ke Settings
CODE — pola request
permission_helper.dart
import 'package:permission_handler/permission_handler.dart';

Future<bool> ensureCamera() async {
  final status = await Permission.camera.status;
  if (status.isGranted) return true;
  if (status.isPermanentlyDenied) {
    await openAppSettings();        // arahkan ke pengaturan
    return false;
  }
  final result = await Permission.camera.request();
  return result.isGranted;
}
💡 UX Rule: minta izin tepat saat dibutuhkan (in-context), bukan semua di awal saat app pertama dibuka.
MINI PROJECT

🏆 Productivity App

Satu aplikasi yang menggabungkan semua 10 fitur menjadi produk nyata.

KONSEP APLIKASI

📋 Fitur Inti

  • Buat & kelola tugas (CRUD)
  • Lampirkan foto dari kamera/galeri
  • Lampirkan file (PDF/dokumen)
  • Reminder via local notification
  • Push dari server saat deadline

🚀 Fitur Lanjutan

  • Export PDF laporan tugas
  • Share laporan ke WhatsApp/email
  • Deep link ke detail tugas
  • URL launcher ke link referensi
  • PDF viewer baca lampiran
ANALOGI BERGERAK
🏗️
📷
📄
🔔
📤
PENJELASAN ANALOGI

Sampai sini kamu sudah punya semua komponen (📷📄🔔📤). Mini project adalah jalur perakitan (🏗️) yang menyatukan komponen-komponen itu menjadi satu kendaraan utuh yang bisa dikendarai pengguna.

MINI PROJECT · ARSITEKTUR

🧱 Arsitektur Productivity App

POINT MATERI

Gunakan Clean Architecture (BAB 6) + Riverpod (BAB 5). Fitur device dibungkus sebagai Service di layer data, dipanggil lewat Provider.

FLOWCHART ANIMASI — Aliran Data
🖼️UI / Widget (TaskPage)
🎛️Riverpod Notifier (TaskController)
📦Repository (TaskRepository)
🔌Services: Camera · File · Pdf · Share · Notif
CODE — struktur folder
lib/ structure
lib/
├─ features/task/
│  ├─ presentation/   # TaskPage, TaskDetailPage (deep link target)
│  ├─ application/     # TaskController (Riverpod Notifier)
│  ├─ domain/          # Task entity, TaskRepository (abstract)
│  └─ data/            # TaskRepositoryImpl + local storage
├─ core/services/
│  ├─ camera_service.dart      # ① ②  image_picker
│  ├─ file_service.dart        # ③     file_picker
│  ├─ pdf_service.dart         # ④ ⑤  view + generate
│  ├─ share_service.dart       # ⑥     share_plus
│  ├─ notification_service.dart# ⑦ ⑧  push + local
│  └─ link_service.dart        # ⑨ ⑩  deep link + launcher
└─ main.dart                   # init Firebase, notif, deep link
💡 Membungkus fitur device sebagai Service membuatnya mudah di-mock saat testing dan diganti tanpa menyentuh UI.
MINI PROJECT · IMPLEMENTASI

⚙️ Kode Inti — Menyatukan Fitur

CODE — entity & controller
task_controller.dart
class Task {
  final String id, title;
  final String? photoPath, filePath;
  final DateTime? reminder;
  Task({required this.id, required this.title,
        this.photoPath, this.filePath, this.reminder});
}

class TaskController extends StateNotifier<List<Task>> {
  TaskController(this._cam, this._notif) : super([]);
  final CameraService _cam;
  final NotificationService _notif;

  Future<void> addWithPhoto(String title, DateTime when) async {
    final photo = await _cam.takePhoto();                 // ①
    final task = Task(
      id: DateTime.now().millisecondsSinceEpoch.toString(),
      title: title, photoPath: photo?.path, reminder: when);
    state = [...state, task];
    await _notif.scheduleReminder(when, title);            // ⑧
  }
}
CODE — export & share laporan
report_action.dart
Future<void> exportAndShare(List<Task> tasks) async {
  final path = await PdfService().buildReport(tasks);  // ⑤ generate
  await ShareService().shareFile(path);                // ⑥ share
}
DEMO VISUAL — Tampilan App
🏆 Productivity App
📷
Desain Poster

⏰ 07:00 · 📎 foto terlampir

📄
Laporan Bulanan

⏰ 13:00 · 📎 laporan.pdf

Belajar BAB 10

Selesai 🎉

✅ laporan.pdf dibagikan
RANGKUMAN

🧭 Rangkuman BAB 10

Sebelum tugas, pastikan kamu menguasai peta fitur & paket kuncinya.

ANALOGI BERGERAK
🏆
PENJELASAN

Kamu telah merakit kendaraan produksimu (🏆). Tabel di bawah adalah buku manual-nya — fitur, paket, dan kapan dipakai.

📦 Paket Kunci

  • image_picker → kamera & galeri
  • file_picker → pilih file
  • flutter_pdfview → baca PDF
  • pdf + printing → buat PDF
  • share_plus → berbagi

📦 Paket Kunci (2)

  • firebase_messaging → push
  • flutter_local_notifications → reminder
  • app_links → deep link
  • url_launcher → buka eksternal
  • permission_handler → izin runtime
🎓 Prinsip emas: minta izin in-context, bungkus fitur sebagai Service, kompres media sebelum upload, dan beri feedback (toast/loading) di setiap aksi device.
✔ 10 Fitur✔ Permission✔ Mini Project✔ Clean Arch
📝 TUGAS 1 EASY

Tugas 1 — Photo Note

ANALOGI BERGERAK
📸
📝
DESKRIPSI

Buat aplikasi Photo Note sederhana: ambil foto dari kamera atau galeri, lalu tampilkan foto beserta catatan teks yang diketik pengguna.

🎯 Kriteria Wajib

  • Tombol "Kamera" dan "Galeri" memakai image_picker
  • Foto terpilih ditampilkan dengan Image.file
  • TextField untuk menulis catatan di bawah foto
  • Tangani kasus user membatalkan pemilihan (null safety)
  • Kompres foto dengan imageQuality: 70
💡 Lihat Petunjuk

Gunakan satu fungsi _pick(ImageSource source) agar tombol kamera & galeri berbagi kode yang sama. Simpan hasil di File? _image dan panggil setState.

📤 Output: Screenshot app + file main.dart. Estimasi: 30–45 menit.
📝 TUGAS 2 MEDIUM

Tugas 2 — Daily Reminder

ANALOGI BERGERAK
🔔
DESKRIPSI

Buat aplikasi Daily Reminder: pengguna mengetik judul pengingat & memilih waktu, lalu app menjadwalkan local notification yang berbunyi pada waktu tersebut.

🎯 Kriteria Wajib

  • Inisialisasi flutter_local_notifications + timezone
  • Minta izin POST_NOTIFICATIONS (Android 13+) saat runtime
  • showTimePicker untuk memilih jam
  • Jadwalkan dengan zonedSchedule
  • Daftar pengingat aktif tampil di layar (ListView)
💡 Lihat Petunjuk

Jangan lupa tz.initializeTimeZones() di main(). Gunakan AndroidScheduleMode.exactAllowWhileIdle agar notifikasi tetap akurat saat device idle.

⚠️ Uji di device asli — emulator kadang menunda alarm idle.
📤 Output: Video singkat notifikasi muncul + kode service. Estimasi: 1–1.5 jam.
📝 TUGAS 3 HARD · CHALLENGE

Tugas 3 — Report & Share

ANALOGI BERGERAK
⌨️
📄
📤
DESKRIPSI

Gabungkan beberapa fitur: dari daftar tugas, generate PDF laporan, tampilkan di PDF viewer, lalu share file ke aplikasi lain.

ALUR YANG HARUS DIBANGUN (FLOWCHART)
📋List Tugas
🖨️Generate PDF
📖PDF Viewer
📤Share

🎯 Kriteria Wajib

  • Generate PDF berisi judul, tabel tugas, & tanggal (paket pdf)
  • Simpan PDF ke folder lokal (path_provider)
  • Buka hasil di flutter_pdfview
  • Tombol share file PDF dengan share_plus
  • Bonus: tombol "Hubungi Guru" pakai url_launcher (mailto)
💡 Lihat Petunjuk

Simpan file via getApplicationDocumentsDirectory(), tulis byte dengan File(path).writeAsBytes(await doc.save()). Path yang sama dipakai untuk viewer & share.

📤 Output: Repo lengkap + demo video alur 4 langkah. Estimasi: 2–3 jam. 🏆 Selamat menyelesaikan BAB 10!