<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Amikom Computer Club - Medium]]></title>
        <description><![CDATA[AMCC merupakan UKM yang bergerak dalam bidang keilmuan. Terdiri dari 5 divisi yaitu, divisi web programming, mobile programming, computer network, data science, dan internet of things. AMCC memiliki visi “The Best IT Organization in Jogja”. - Medium]]></description>
        <link>https://medium.com/amcc-amikom?source=rss----fe834c257357---4</link>
        <image>
            <url>https://cdn-images-1.medium.com/proxy/1*TGH72Nnw24QL3iV9IOm4VA.png</url>
            <title>Amikom Computer Club - Medium</title>
            <link>https://medium.com/amcc-amikom?source=rss----fe834c257357---4</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Thu, 12 Nov 2026 04:54:16 GMT</lastBuildDate>
        <atom:link href="https://medium.com/feed/amcc-amikom" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[Pengantar TinyML: Menggabungkan Kecerdasan Buatan dan Mikrokontroler untuk Edge Computing]]></title>
            <link>https://medium.com/amcc-amikom/pengantar-tinyml-menggabungkan-kecerdasan-buatan-dan-mikrokontroler-untuk-edge-computing-0c47a35112db?source=rss----fe834c257357---4</link>
            <guid isPermaLink="false">https://medium.com/p/0c47a35112db</guid>
            <category><![CDATA[internet-of-things]]></category>
            <category><![CDATA[tinyml]]></category>
            <category><![CDATA[hardware]]></category>
            <category><![CDATA[hardware-software]]></category>
            <dc:creator><![CDATA[GDE AGUNG ANANDA CANDRA DINATA 24.11.6174]]></dc:creator>
            <pubDate>Sat, 05 Sep 2026 11:52:48 GMT</pubDate>
            <atom:updated>2026-09-05T11:52:49.358Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Wa0TNLq1WA4__mOJ4z0HJA.png" /></figure><p>Halo IoT Gengs! 👋 Selamat datang kembali pada modul pelatihan divisi Internet Of Things AMCC! Setelah sebelumnya kita menjelajahi pengiriman data jarak jauh menggunakan LoRa dan LoRaWAN, kali ini kita akan menaikkan level kemampuan kita ke ranah Edge AI &amp; Machine Learning.</p><p>Di pertemuan ini, kita akan membahas <strong>TinyML (Tiny Machine Learning)</strong> — sebuah teknologi revolusioner yang memungkinkan model <em>Machine Learning</em> berjalan langsung di atas mikrokontroler berdaya sangat rendah (seperti ESP32) tanpa memerlukan koneksi internet atau server <em>cloud</em>.</p><p>Teori tanpa praktek tentu kurang seru. Oleh karena itu, kita akan langsung mencoba <em>deployment</em> model TinyML ke dalam sebuah sistem permainan: <strong>Game Suit (Batu-Gunting-Kertas) Pintar berbasis ESP32!</strong> Buckle up, and let’s dive in!</p><p><strong>Table of Contents</strong></p><p>∘ <a href="#bda0">Let’s Talk About TinyML</a><br> ∘ <a href="#ae83">Apa itu TinyML?</a><br> ∘ <a href="#5872">Mengapa Menjalankan Machine Learning di Edge/Mikrokontroler?</a><br> ∘ <a href="#fbb6">Use Case Nyata TinyML di Industri IoT</a><br> ∘ <a href="#831a">Alur Kerja (Workflow) Pembuatan Model TinyML</a><br> ∘ <a href="#bb24">Study Case: Game Suit TinyML ESP32</a><br> ∘ <a href="#e4bd">Persiapan Library &amp; Wokwi Simulator</a><br> ∘ <a href="#cd15">Rangkaian Komponen &amp; ESP32</a><br> ∘ <a href="#6767">The Code: Eksekusi Model di ESP32</a><br> ∘ <a href="#a424">Cara Menjalankan Simulasi</a><br> ∘ <a href="#5eb9">Penutup</a></p><h4>Let’s Talk About TinyML</h4><p>Selama ini, kita terbiasa mengirim data mentah dari sensor (seperti suhu atau getaran) melalui MQTT ke <em>cloud</em> atau server lokal untuk diproses. Namun, pendekatan konvensional ini memiliki kelemahan besar: latensi jaringan yang tinggi, kerentanan terhadap putusnya koneksi internet, serta masalah privasi data karena seluruh informasi harus dikirim ke luar perangkat.</p><p>Dengan TinyML, paradigma tersebut bergeser. Model kecerdasan buatan dipangkas dan dioptimasi sedemikian rupa hingga ukurannya cukup kecil (dalam satuan <em>kilobyte</em>) untuk dimuat ke dalam memori Flash mikrokontroler. Hasilnya? Perangkat IoT Anda dapat berpikir, mengenali pola suara, mendeteksi getaran anomali, atau membaca gestur secara mandiri (<em>on-device</em>) secara <em>real-time</em>.</p><h4><strong>Apa itu TinyML?</strong></h4><p>TinyML adalah sub-bidang dari <em>Machine Learning</em> dan <em>Embedded Systems</em> yang berfokus pada perancangan, pelatihan, kuantisasi, dan penyebaran model pembelajaran mesin (biasanya menggunakan TensorFlow Lite for Microcontrollers) pada perangkat keras berdaya rendah yang berjalan dengan konsumsi daya di bawah 1 mW.</p><h4><strong>Mengapa Menjalankan Machine Learning di Edge/Mikrokontroler?</strong></h4><ul><li><strong>Ultra-Low Latency:</strong> Keputusan diambil dalam hitungan milidetik tanpa harus menunggu respons dari server <em>cloud</em>.</li><li><strong>Privasi dan Keamanan Tinggi:</strong> Data sensitif (seperti rekaman suara atau video visual) diproses secara lokal di perangkat dan tidak pernah meninggalkan mikrokontroler.</li><li><strong>Efisiensi Bandwidth &amp; Biaya:</strong> Tidak ada kebutuhan pengiriman data telemetri secara terus-menerus ke <em>cloud</em>, sehingga menghemat kuota internet dan infrastruktur server.</li><li><strong>Keandalan Tinggi (Offline Capability):</strong> Sistem tetap berfungsi secara optimal meskipun berada di area <em>blank spot</em> tanpa koneksi internet.</li></ul><h4><strong>Use Case Nyata TinyML di Industri IoT</strong></h4><ul><li><strong>Predictive Maintenance:</strong> Mendeteksi getaran anomali pada motor induksi atau mesin pabrik menggunakan akselerometer untuk mencegah kerusakan fatal sebelum terjadi.</li><li><strong>Smart Home &amp; Voice Recognition:</strong> Mengenali kata kunci suara tertentu (seperti perintah “Nyalakan Lampu”) secara lokal pada perangkat tanpa mengirim audio ke server luar.</li><li><strong>Environmental Monitoring:</strong> Klasifikasi tingkat keparahan polusi atau deteksi dini kebakaran berdasarkan kombinasi pembacaan pola multi-sensor.</li></ul><h4><strong>Alur Kerja (Workflow) Pembuatan Model TinyML</strong></h4><ul><li><strong>Data Collection:</strong> Mengumpulkan data sampel.</li><li><strong>Model Training:</strong> Melatih model jaringan saraf tiruan (Neural Network) menggunakan Python/TensorFlow (biasanya di Google Colab atau PC lokal).</li><li><strong>Model Quantization / Conversion:</strong> Mengubah model menjadi format .tflite (TensorFlow Lite), lalu di- <em>convert</em> menjadi <em>Array Byte</em> C/C++ agar bisa dibaca mikrokontroler.</li><li><strong>Deployment:</strong> Mengunggah <em>Array Byte</em> tersebut ke ESP32 bersama kode inferensi (pembacaan model).</li></ul><h4>Study Case: Game Suit TinyML ESP32</h4><p>Pada modul ini, kita akan fokus pada tahap <strong>Deployment</strong>. Kita akan membuat Game Batu-Gunting-Kertas. Pemain akan menekan salah satu dari tiga tombol. Mikrokontroler tidak langsung mengecek tombol mana yang ditekan menggunakan logika if-else biasa, melainkan <strong>memasukkan data <em>encoding</em> tombol tersebut ke dalam Model TensorFlow Lite Micro</strong>.</p><p>Model akan memprediksi pilihan kita (Batu, Gunting, atau Kertas). Setelah itu, CPU (ESP32) akan memilih langkah lawan secara acak, menentukan pemenangnya, dan menampilkannya di layar OLED 128x128!</p><blockquote><strong><em>Catatan Penting:</em></strong><em> Karena fokus kita adalah belajar alur deployment (alur memasukkan model AI ke mikrokontroler), data input yang digunakan adalah </em>encoding<em> tombol sederhana, bukan pengukuran sensor kamera atau gestur jari nyata.</em></blockquote><h4>Persiapan Library &amp; Wokwi Simulator</h4><p>Untuk memudahkan, kita akan menggunakan <strong>Wokwi Simulator</strong>.</p><ol><li>Buka <a href="https://wokwi.com/projects/new/esp32">Wokwi ESP32</a>.</li><li>Di bagian <strong>Library Manager</strong> (atau file libraries.txt), pastikan kalian menambahkan library berikut:</li></ol><ul><li>Adafruit GFX Library</li><li>Adafruit SH110X</li><li>Adafruit BusIO</li><li>Chirale_TensorFLowLite (Ini adalah library utama untuk menjalankan TFLite di ESP32!)</li></ul><h4>Rangkaian Komponen &amp; ESP32</h4><p>Jika kalian ingin merangkainya secara fisik, siapkan komponen dan hubungkan sesuai <em>pinout</em> berikut (bisa juga kita simulasikan di Wokwi nanti):</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*IAFotxLs8AkI1ZqcPGZBIg.png" /></figure><h4>The Code: Eksekusi Model di ESP32</h4><p>Dalam proyek TinyML, kita biasanya memisahkan file model (<em>Array Byte</em>) dari program utama agar rapi, Teman teman dapat mengakses code nya pada <a href="https://github.com/phiesanggoreng/suittinyml.git">https://github.com/phiesanggoreng/suittinyml.git</a>.</p><h4>Cara Menjalankan Simulasi</h4><ol><li>Pastikan semua <em>code</em> di Wokwi (sketch.ino, game_logic.h, model_data.h, dan diagram.json) sudah sama persis dengan <em>repository</em> Github.</li><li>Klik tombol <strong>Start Simulation</strong> (Hijau).</li><li>Saat OLED menampilkan tulisan “PILIH”, klik salah satu tombol <em>pushbutton</em> berwarna (Merah = Batu, Kuning = Gunting, Biru = Kertas).</li><li>ESP32 akan menggunakan model TensorFlow Lite untuk memproses inputmu, memilih lawannya, lalu membunyikan buzzer dan menampilkan hasil kemenangan lengkap dengan <em>Scoreboard</em>!</li></ol><h4>Penutup</h4><p>Gimana IoT Gengs? Lewat studi kasus Game Suit ini, kita sudah berhasil melakukan tahap <em>deployment</em> memindahkan “otak” buatan (model .tflite) yang dilatih di komputer besar ke dalam mikrokontroler sekelas ESP32!</p><p>Ini membuktikan bahwa dengan TinyML, perangkat keras sederhana kita kini memiliki kapasitas “berpikir” secara mandiri tanpa bergantung pada jaringan internet luar. Teknologi ini membuka peluang sangat luas untuk menciptakan sistem otomatisasi yang lebih responsif, privat, dan canggih di masa depan.</p><p>Kalau ada yang <em>error</em> saat <em>ngoding</em> atau kompilasi gagal karena memori tidak cukup, jangan panik. <em>Debug is part of the fun!</em> Langsung aja kita diskusikan di kelas.</p><p>Sampai jumpa………</p><p><strong>AMCC</strong></p><p><em>Learning By Doing</em></p><p><em>Learning By Teaching</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=0c47a35112db" width="1" height="1" alt=""><hr><p><a href="https://medium.com/amcc-amikom/pengantar-tinyml-menggabungkan-kecerdasan-buatan-dan-mikrokontroler-untuk-edge-computing-0c47a35112db">Pengantar TinyML: Menggabungkan Kecerdasan Buatan dan Mikrokontroler untuk Edge Computing</a> was originally published in <a href="https://medium.com/amcc-amikom">Amikom Computer Club</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Smart Mobile Apps with Flutter & Gemini AI]]></title>
            <link>https://medium.com/amcc-amikom/smart-mobile-apps-with-flutter-gemini-ai-87b8280f6c07?source=rss----fe834c257357---4</link>
            <guid isPermaLink="false">https://medium.com/p/87b8280f6c07</guid>
            <category><![CDATA[mobile-apps]]></category>
            <category><![CDATA[artificial-intelligence]]></category>
            <dc:creator><![CDATA[Firdaus Chuzaeni]]></dc:creator>
            <pubDate>Wed, 02 Sep 2026 02:01:57 GMT</pubDate>
            <atom:updated>2026-09-02T02:01:56.103Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Welbj1-2Zs9HoTKGXv26Xg.png" /></figure><p>Halo Mobile Rangers! 👋✨</p><p>Kembali lagi bersama kami! Di petualangan kali ini, kita nggak cuma sekadar bikin aplikasi aja, tapi kita bakal melangkah lebih jauh. Kita akan belajar cara menautkan aplikasi yang sudah dibuat dengan salah satu AI paling populer saat ini: <strong>Gemini AI!</strong> 🚀🤖</p><p>Penasaran gimana caranya bikin aplikasi kamu jadi jauh lebih pintar? Yuk, simak panduan lengkapnya di bawah ini!</p><p>· <a href="#8aac">GEMINI API</a><br>· <a href="#1c08">Membuat GEMINI API KEY</a><br>· <a href="#b3c3">Apa itu Konteks dalam AI ?</a><br>· <a href="#eabc">Membuat UI Aplikasi dan Menerapkan Gemini API ke dalamnya.</a></p><h3><strong>GEMINI API</strong></h3><p>Sebelum kita lanjut ke sesi praktikum, penting banget buat kita pahami dulu bagaimana cara menghubungkan aplikasi dengan Gemini AI. Jawabannya adalah dengan menggunakan <strong>Gemini API</strong>!</p><p>Dalam dunia pengembangan aplikasi, “pintu akses” sakti ini dikenal dengan nama <strong>Gemini API</strong> <em>(Application Programming Interface)</em>! 🔑✨</p><p>Bayangkan Gemini API ini sebagai <strong>jembatan super cepat</strong> atau <strong>kunci pasport digital</strong> yang membuka gerbang kecerdasan Google untuk langsung masuk dan ‘tinggal’ di dalam aplikasi buatanmu.</p><p><strong>Mengapa Gemini API Sangat Spesial?</strong></p><ul><li><strong>Otak Tambahan untuk Aplikasimu</strong> <br>Kamu tidak perlu membuat model AI sendiri dari nol yang memakan waktu berbulan-bulan. Cukup “panggil” Gemini API, dan aplikasimu langsung punya kemampuan menganalisis teks, menerjemahkan bahasa, hingga memahami gambar.</li><li><strong>Komunikasi Dua Arah yang Kilat</strong> <br>Aplikasi milikmu cukup mengirim pesan (disebut <em>Request/Prompt</em>), dan dalam hitungan detik Gemini akan menjawabnya (<em>Response</em>) untuk ditampilkan langsung kepada pengguna.</li><li><strong>Fleksibel &amp; Siap Pakai</strong> 🛠<br>Bisa dipasang di platform mana saja — mulai dari aplikasi mobile (Flutter, React Native, Android/iOS native) hingga aplikasi web dan server.</li></ul><p>Setelah kita mengetahui apa itu Gemini API, langsung saja kita masuk ke praktikum nya</p><h3><strong>Membuat GEMINI API KEY</strong></h3><ol><li><strong>Membuka Google AI Studio </strong>( <a href="https://aistudio.google.com/">https://aistudio.google.com</a> )</li><li><strong>Membuat API Key baru</strong></li></ol><p>a. Jika sudah membuka Google AI Studio, teman-teman bisa klik tombol dashboard pada menu sebelah kiri.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/223/1*TovUe4f85yW1A8efbBEjGg.jpeg" /></figure><p>b. Jika tombol dashboard sudah diklik, maka tampilannya akan seperti ini.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*yw94FOS7LKnl4O2ohx74QA.jpeg" /></figure><p>c. Kemudian, klik pada tombol <strong>Create API key</strong> (pojok atas sebelah kanan).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Zls2YoFZVe_FEsi_RxrTvw.jpeg" /></figure><p>d. Buat nama untuk API Key serta Buat Project jika belum ada. Nanti hasilnya seperti contoh di bawah ini.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/509/1*UKKkdvwbXEPjutRYfgRlZw.jpeg" /></figure><p>e. Dan klik Create key. Selamat Anda sudah berhasil membuat API Key pertama dan silahkan simpan untuk membuat projek kali ini.</p><h3><strong>Apa itu Konteks dalam AI ?</strong></h3><p>Sebelum kita lanjut ke praktikum kedua, ada satu hal penting yang harus kalian ketahui. Dalam proses pengembangan aplikasi, kita menginginkan supaya AI/Chatbot yang dibuat menjawab sesuai dengan tema aplikasi. Oleh karena itu, kita memerlukan adanya Konteks dalam AI.</p><p>Bayangkan kamu lagi asyik ngobrol sama teman, tapi tiba-tiba kamu melempar kalimat tanpa arah dan latar belakang yang jelas. Kira-kira temanmu bakal paham? Jawabannya: pasti bingung banget! 🥲</p><p>Nah, hal yang sama persis berlaku di dunia <strong>Artificial Intelligence</strong>.</p><p>Dalam pengembangan AI khususnya pada Large Language Models (LLM) seperti <strong>Gemini</strong>, konteks adalah <strong>fondasi utamanya</strong>. Konteks merupakan seluruh informasi latar belakang, riwayat instruksi, dan detail pendukung yang kita berikan kepada AI agar ia dapat memproses maksud pesan kita dengan tepat serta memberikan jawaban yang relevan dan akurat.</p><p><strong>Mengapa Konteks Sangat Krusial?</strong></p><ul><li><strong>Menghindari Perbedaan Penafsiran <em>(Ambiguity)<br></em></strong>Kata yang sama bisa memiliki arti berbeda. Konteks membantu AI memahami makna spesifik dari perintah yang diberikan.</li><li><strong>Menjaga “Ingatan” Percakapan</strong><br>Dengan riwayat konteks yang baik, AI dapat mengingat alur diskusi sebelumnya tanpa perlu kamu ulang dari awal.</li><li><strong>Hasil Response Lebih Presisi</strong><br>Semakin jelas latar belakang data atau peran yang kamu berikan <em>(System Prompt)</em>, semakin pas pula jawaban yang dihasilkan AI sesuai kebutuhan aplikasimu.</li></ul><p><strong>Elemen Utama Konteks dalam AI</strong></p><ul><li><strong>Instruksi / System Prompt</strong>: Aturan dasar yang menentukan peran AI (misal: “Bertindaklah sebagai tutor pemrograman mobile yang ramah”).</li><li><strong>Riwayat Percakapan <em>(Chat History)</em></strong>: Pesan-pesan sebelumnya dalam sesi obrolan agar AI ingat apa yang sudah dibahas.</li><li><strong>Data Tambahan <em>(External Data / RAG)</em></strong>: Dokumen, data pengguna, atau teks referensi yang dimasukkan ke dalam prompt agar jawaban AI akurat.</li></ul><h3><strong>Membuat UI Aplikasi dan Menerapkan Gemini API ke dalamnya.</strong></h3><ol><li>Menyalin project Mobile Programming sebelumnya.</li></ol><pre>git clone -b pertemuan-11 https://github.com/amccamikom/amcc-mobile-2025.git</pre><p>2. Menginstal library/package yang diperlukan.</p><pre>flutter pub add google_generative_ai flutter_dotenv flutter_markdown shimmer</pre><p>3. Menambahkan assets .env untuk meletakkan Gemini API Key supaya tetap aman.</p><p>4. Membuat file .env dan mengisinya dengan Gemini API Key</p><p>Contoh:</p><pre>GEMINI_API_KEY=ISI_DENGAN_GEMINIKEY_MILIKMU</pre><p>5. Membuat file <strong>gemini_services.dart</strong> pada <strong>folder services</strong> sebagai tempat fungsi chatbot berada.</p><pre>import &#39;package:flutter_dotenv/flutter_dotenv.dart&#39;;<br>import &#39;package:google_generative_ai/google_generative_ai.dart&#39;;<br>import &#39;task_service.dart&#39;;<br><br>class GeminiService {<br>  late final GenerativeModel _model;<br>  late final ChatSession _chat;<br>  final TaskService _taskService = TaskService();<br><br>  GeminiService() {<br>    // Membaca API Key dari file .env<br>    final apiKey = dotenv.env[&#39;GEMINI_API_KEY&#39;] ?? &#39;&#39;;<br><br>    if (apiKey.isEmpty) {<br>      throw Exception(&#39;API Key Gemini tidak ditemukan di file .env&#39;);<br>    }<br><br>    // Inisialisasi Model Gemini dengan Konteks Peran<br>    _model = GenerativeModel(<br>      model: &#39;gemini-3.6-flash&#39;,<br>      apiKey: apiKey,<br>      systemInstruction: Content.system(<br>        &#39;Kamu adalah asisten AI ramah khusus untuk membantu kebutuhan mahasiswa. Kamu memiliki akses ke daftar tugas/task pengguna yang tersimpan di database Firestore aplikasi.&#39;,<br>      ),<br>    );<br><br>    // Memulai sesi obrolan agar konteks percakapan sebelumnya tersimpan<br>    _chat = _model.startChat();<br>  }<br><br>  /// Mengirim pesan ke Gemini AI dan mengembalikan respon berupa teks<br>  Future&lt;String?&gt; sendMessage(String message) async {<br>    try {<br>      // Mengambil daftar tugas terbaru pengguna dari Firestore<br>      final tasks = await _taskService.getTasksOnce();<br><br>      String contextText = &#39;&#39;;<br>      if (tasks.isNotEmpty) {<br>        final taskList = tasks<br>            .map((t) =&gt;<br>                &quot;- Judul: ${t.title} | Tanggal: ${t.date} | Status: ${t.isCompleted ? &#39;Selesai&#39; : &#39;Belum Selesai&#39;}&quot;)<br>            .join(&#39;\n&#39;);<br>        contextText =<br>            &quot;[Daftar Tugas Pengguna di Firestore saat ini]:\n$taskList\n\n&quot;;<br>      } else {<br>        contextText =<br>            &quot;[Daftar Tugas Pengguna di Firestore saat ini]: Belum ada tugas yang tersimpan.\n\n&quot;;<br>      }<br><br>      final fullPrompt = &quot;$contextText[Pesan/Pertanyaan Pengguna]: $message&quot;;<br>      final response = await _chat.sendMessage(Content.text(fullPrompt));<br>      return response.text;<br>    } catch (e) {<br>      throw Exception(&#39;Gagal mengirim pesan ke Gemini: $e&#39;);<br>    }<br>  }<br>}</pre><p>6. Membuat menu dan menambahkan rute untuk halaman chatbot</p><pre>PersistentTabConfig(<br>          screen: const ChatbotPage(),<br>          item: ItemConfig(<br>            icon: const Icon(Icons.chat_bubble_outline),<br>            title: &quot;Chatbot&quot;,<br>            activeForegroundColor: const Color(0xFF3254FD),<br>            inactiveForegroundColor: Colors.grey,<br>          ),<br>        ),</pre><p>7. Membuat file <strong>chatbot_page.dart</strong> untuk tampilan halaman berinteraksi dengan chatbot</p><pre>import &#39;package:flutter/material.dart&#39;;<br>import &#39;package:flutter_markdown/flutter_markdown.dart&#39;;<br>import &#39;package:shimmer/shimmer.dart&#39;;<br>import &#39;../services/gemini_service.dart&#39;;<br><br>class ChatbotPage extends StatefulWidget {<br>  const ChatbotPage({super.key});<br><br>  @override<br>  State&lt;ChatbotPage&gt; createState() =&gt; _ChatbotPageState();<br>}<br><br>class _ChatbotPageState extends State&lt;ChatbotPage&gt; {<br>  final TextEditingController _controller = TextEditingController();<br>  final ScrollController _scrollController = ScrollController();<br>  final List&lt;Map&lt;String, String&gt;&gt; _messages = [<br>    {<br>      &#39;sender&#39;: &#39;Bot AI&#39;,<br>      &#39;text&#39;: &#39;Selamat datang! ChatBot siap membantumu!&#39;,<br>    }<br>  ];<br><br>  late final GeminiService _geminiService;<br>  bool _isLoading = false;<br><br>  @override<br>  void initState() {<br>    super.initState();<br>    _geminiService = GeminiService();<br>  }<br><br>  @override<br>  void dispose() {<br>    _controller.dispose();<br>    _scrollController.dispose();<br>    super.dispose();<br>  }<br><br>  void _scrollToBottom() {<br>    WidgetsBinding.instance.addPostFrameCallback((_) {<br>      if (_scrollController.hasClients) {<br>        _scrollController.animateTo(<br>          _scrollController.position.maxScrollExtent,<br>          duration: const Duration(milliseconds: 300),<br>          curve: Curves.easeOut,<br>        );<br>      }<br>    });<br>  }<br><br>  Future&lt;void&gt; _handleSendMessage() async {<br>    final text = _controller.text.trim();<br>    if (text.isEmpty) return;<br><br>    setState(() {<br>      _messages.add({&#39;sender&#39;: &#39;user&#39;, &#39;text&#39;: text});<br>      _isLoading = true;<br>    });<br><br>    _controller.clear();<br>    _scrollToBottom();<br><br>    try {<br>      final responseText = await _geminiService.sendMessage(text);<br><br>      setState(() {<br>        _messages.add({<br>          &#39;sender&#39;: &#39;Bot AI&#39;,<br>          &#39;text&#39;: responseText ?? &#39;Tidak ada tanggapan.&#39;,<br>        });<br>      });<br>    } catch (e) {<br>      setState(() {<br>        _messages.add({<br>          &#39;sender&#39;: &#39;Bot AI&#39;,<br>          &#39;text&#39;: &#39;Terjadi kesalahan: $e&#39;,<br>        });<br>      });<br>    } finally {<br>      setState(() {<br>        _isLoading = false;<br>      });<br>      _scrollToBottom();<br>    }<br>  }<br><br>  @override<br>  Widget build(BuildContext context) {<br>    return Scaffold(<br>      backgroundColor: const Color(0xFFF8F9FA),<br>      appBar: AppBar(<br>        title: Row(<br>          mainAxisSize: MainAxisSize.min,<br>          children: const [<br>            Icon(Icons.smart_toy_outlined, color: Colors.blue),<br>            SizedBox(width: 8),<br>            Text(<br>              &#39;ChatBot&#39;,<br>              style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18),<br>            ),<br>          ],<br>        ),<br>        backgroundColor: Colors.white,<br>        foregroundColor: Colors.black,<br>        elevation: 1,<br>      ),<br>      body: Column(<br>        children: [<br>          // Daftar Balon Chat<br>          Expanded(<br>            child: ListView.builder(<br>              controller: _scrollController,<br>              padding: const EdgeInsets.all(16.0),<br>              itemCount: _messages.length + (_isLoading ? 1 : 0),<br>              itemBuilder: (context, index) {<br>                if (index == _messages.length &amp;&amp; _isLoading) {<br>                  // Shimmer Loading Chat Bubble<br>                  return Align(<br>                    alignment: Alignment.centerLeft,<br>                    child: Container(<br>                      margin: const EdgeInsets.only(bottom: 12.0),<br>                      padding: const EdgeInsets.all(14.0),<br>                      constraints: BoxConstraints(<br>                        maxWidth: MediaQuery.of(context).size.width * 0.75,<br>                      ),<br>                      decoration: BoxDecoration(<br>                        color: Colors.white,<br>                        borderRadius: const BorderRadius.only(<br>                          topLeft: Radius.circular(16),<br>                          topRight: Radius.circular(16),<br>                          bottomRight: Radius.circular(16),<br>                          bottomLeft: Radius.circular(4),<br>                        ),<br>                        border: Border.all(color: Colors.grey.shade300),<br>                        boxShadow: [<br>                          BoxShadow(<br>                            color: Colors.black.withValues(alpha: 0.03),<br>                            blurRadius: 4,<br>                            offset: const Offset(0, 2),<br>                          )<br>                        ],<br>                      ),<br>                      child: Shimmer.fromColors(<br>                        baseColor: Colors.grey.shade300,<br>                        highlightColor: Colors.grey.shade100,<br>                        child: Column(<br>                          crossAxisAlignment: CrossAxisAlignment.start,<br>                          mainAxisSize: MainAxisSize.min,<br>                          children: [<br>                            Container(<br>                              width: double.infinity,<br>                              height: 12.0,<br>                              decoration: BoxDecoration(<br>                                color: Colors.white,<br>                                borderRadius: BorderRadius.circular(6),<br>                              ),<br>                            ),<br>                            const SizedBox(height: 8),<br>                            Container(<br>                              width: MediaQuery.of(context).size.width * 0.5,<br>                              height: 12.0,<br>                              decoration: BoxDecoration(<br>                                color: Colors.white,<br>                                borderRadius: BorderRadius.circular(6),<br>                              ),<br>                            ),<br>                            const SizedBox(height: 8),<br>                            Container(<br>                              width: MediaQuery.of(context).size.width * 0.3,<br>                              height: 12.0,<br>                              decoration: BoxDecoration(<br>                                color: Colors.white,<br>                                borderRadius: BorderRadius.circular(6),<br>                              ),<br>                            ),<br>                          ],<br>                        ),<br>                      ),<br>                    ),<br>                  );<br>                }<br><br>                final message = _messages[index];<br>                final isUser = message[&#39;sender&#39;] == &#39;user&#39;;<br><br>                return Align(<br>                  alignment: isUser ? Alignment.centerRight : Alignment.centerLeft,<br>                  child: Container(<br>                    margin: const EdgeInsets.only(bottom: 12.0),<br>                    padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0),<br>                    constraints: BoxConstraints(<br>                      maxWidth: MediaQuery.of(context).size.width * 0.8,<br>                    ),<br>                    decoration: BoxDecoration(<br>                      color: isUser ? const Color(0xFF3254FD) : Colors.white,<br>                      borderRadius: BorderRadius.only(<br>                        topLeft: const Radius.circular(16),<br>                        topRight: const Radius.circular(16),<br>                        bottomLeft: isUser ? const Radius.circular(16) : const Radius.circular(4),<br>                        bottomRight: isUser ? const Radius.circular(4) : const Radius.circular(16),<br>                      ),<br>                      border: isUser ? null : Border.all(color: Colors.grey.shade300),<br>                      boxShadow: [<br>                        BoxShadow(<br>                          color: Colors.black.withValues(alpha: 0.04),<br>                          blurRadius: 4,<br>                          offset: const Offset(0, 2),<br>                        ),<br>                      ],<br>                    ),<br>                    child: isUser<br>                        ? Text(<br>                            message[&#39;text&#39;]!,<br>                            style: const TextStyle(<br>                              fontSize: 14,<br>                              color: Colors.white,<br>                              height: 1.4,<br>                            ),<br>                          )<br>                        : MarkdownBody(<br>                            data: message[&#39;text&#39;]!,<br>                            selectable: true,<br>                            styleSheet: MarkdownStyleSheet.fromTheme(<br>                              Theme.of(context),<br>                            ).copyWith(<br>                              p: const TextStyle(<br>                                fontSize: 14,<br>                                color: Colors.black87,<br>                                height: 1.4,<br>                              ),<br>                              strong: const TextStyle(<br>                                fontWeight: FontWeight.bold,<br>                                color: Colors.black,<br>                              ),<br>                              em: const TextStyle(<br>                                fontStyle: FontStyle.italic,<br>                              ),<br>                              listBullet: const TextStyle(<br>                                fontSize: 14,<br>                                color: Color(0xFF3254FD),<br>                              ),<br>                            ),<br>                          ),<br>                  ),<br>                );<br>              },<br>            ),<br>          ),<br><br>          // Bar Input Pesan<br>          Container(<br>            padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),<br>            decoration: BoxDecoration(<br>              color: Colors.white,<br>              boxShadow: [<br>                BoxShadow(<br>                  color: Colors.black.withValues(alpha: 0.05),<br>                  blurRadius: 8,<br>                  offset: const Offset(0, -2),<br>                )<br>              ],<br>            ),<br>            child: Row(<br>              children: [<br>                Expanded(<br>                  child: Container(<br>                    padding: const EdgeInsets.symmetric(horizontal: 16),<br>                    decoration: BoxDecoration(<br>                      color: const Color(0xFFF1F3F5),<br>                      borderRadius: BorderRadius.circular(24),<br>                    ),<br>                    child: TextField(<br>                      controller: _controller,<br>                      decoration: const InputDecoration(<br>                        hintText: &#39;Ketik pesan...&#39;,<br>                        border: InputBorder.none,<br>                      ),<br>                      onSubmitted: (_) =&gt; _handleSendMessage(),<br>                    ),<br>                  ),<br>                ),<br>                const SizedBox(width: 8),<br>                CircleAvatar(<br>                  backgroundColor: const Color(0xFF3254FD),<br>                  radius: 20,<br>                  child: IconButton(<br>                    icon: const Icon(Icons.send, color: Colors.white, size: 18),<br>                    onPressed: _handleSendMessage,<br>                  ),<br>                ),<br>              ],<br>            ),<br>          ),<br>        ],<br>      ),<br>    );<br>  }<br>}</pre><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=87b8280f6c07" width="1" height="1" alt=""><hr><p><a href="https://medium.com/amcc-amikom/smart-mobile-apps-with-flutter-gemini-ai-87b8280f6c07">Smart Mobile Apps with Flutter &amp; Gemini AI</a> was originally published in <a href="https://medium.com/amcc-amikom">Amikom Computer Club</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Mengapa Banyak Orang Salah Paham tentang Sentiment Analysis?]]></title>
            <link>https://medium.com/amcc-amikom/mengapa-banyak-orang-salah-paham-tentang-sentiment-analysis-05f317058f42?source=rss----fe834c257357---4</link>
            <guid isPermaLink="false">https://medium.com/p/05f317058f42</guid>
            <category><![CDATA[amikom-computer-club]]></category>
            <category><![CDATA[sentiment-analysis]]></category>
            <category><![CDATA[data-science]]></category>
            <dc:creator><![CDATA[Nakishi]]></dc:creator>
            <pubDate>Sat, 29 Aug 2026 12:00:20 GMT</pubDate>
            <atom:updated>2026-08-29T12:00:18.827Z</atom:updated>
            <content:encoded><![CDATA[<blockquote>Delapan keyakinan yang beredar luas, diuji satu per satu, dan sebagian besar berhasil di bantah.</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/960/1*16WGBhouk2N8VmG09Gmkfw.png" /></figure><p>Kita buka sesi ini dengan satu kalimat</p><blockquote>“Kuah sotonya berminyak banget”</blockquote><p>Apa sentimennya? Positif? Negatif? Netral? Atau tergantung?</p><p>Sekarang bandingkan dengan kalimat berikut.</p><blockquote>“Makanannya enak banget, recommended sih”</blockquote><p>Kalimat kedua ini tidak perlu ditanyakan. Kita semua sepakat bahwa sentimennya positif.</p><p>Perbedaannya bukan terletak pada panjang kalimat atau topiknya, melainkan pada jenis kata sifat yang dipakai. Kata “enak” bersifat evaluatif karena sudah mengandung putusan di dalamnya. Sementara itu kata “berminyak” bersifat deskriptif, karena ia hanya menunjuk sesuatu yang bisa dilihat di permukaan mangkuk tanpa menilai apa pun.</p><p>Kalimat kedua membawa valensinya sendiri, sedangkan kalimat pertama tidak. Dari titik inilah seluruh persoalan bermula.</p><h3><strong>Delapan keyakinan yang akan diuji</strong></h3><p>Tulisan ini bukan tutorial. Anggap saja sebagai laporan pengujian. Semua angka yang muncul di dalamnya saya jalankan sendiri, bukan disalin dari tutorial lain, dan notebook untuk memeriksanya tersedia di bagian penutup.</p><p>Keyakinan-keyakinan tersebut adalah:</p><ol><li>Sentiment analysis adalah teknik mengekstrak emosi dari teks</li><li>Stopword adalah kata yang tidak penting, jadi aman dibuang</li><li>Case folding bersifat netral, hanya menyeragamkan huruf</li><li>Kata <strong>beruang </strong>akan di-stem menjadi <strong>uang</strong></li><li>Lemmatization mengatasi masalah over-stemming</li><li>Lexicon adalah kamus yang sudah divalidasi, tinggal dipakai</li><li>Kalau lexicon kurang akurat, tinggal tambahkan aturan</li><li>Akurasi yang tinggi berarti modelnya sudah bagus</li></ol><p>Kedelapannya bermasalah. Sebagian keliru sepenuhnya, sebagian lagi kebetulan benar tetapi karena alasan yang salah.</p><p>Yang lebih menarik adalah bahwa kedelapannya punya sebab yang sama. Bagian itu saya bahas di penutup.</p><h3>Salah paham pertama: “Mengekstrak emosi dari teks”</h3><p>Definisi yang paling sering ditemui kira-kira berbunyi begini.</p><blockquote>Sentiment analysis adalah teknik mengekstrak emosi dari teks menggunakan NLP.</blockquote><p>Sebagai titik awal, kalimat itu tidak buruk. Namun ada dua kata di dalamnya yang tidak sesuai dengan makna aslinya, dan keduanya berkonsekuensi.</p><h4>Emosi bukanlah sentimen</h4><p>Sentimen adalah valensi, yaitu arah dan derajat sebuah evaluasi. Emosi adalah kategori afektif, seperti senang, marah, takut, atau jijik. Keduanya berbeda, dengan taksonomi, dataset, dan task masing-masing.</p><p>Buktinya mudah ditemukan. Dua kalimat berikut memisahkan keduanya dengan bersih.</p><p>Kalimat:</p><blockquote>“Produk ini tidak sesuai dengan spesifikasi yang dijanjikan”</blockquote><p>Jelas bersentimen negatif, tetapi emosinya nyaris nol. Nadanya datar. Sebaliknya, kalimat:</p><blockquote>“GILA GUE GA NYANGKA INI KEJADIAN”</blockquote><p>Penuh emosi, tetapi polaritasnya ambigu. Bisa berarti girang, bisa juga berarti murka.</p><p>Jika Anda mencari dataset dengan asumsi bahwa keduanya sama, Anda bisa jadi akan tersesat ke label set yang keliru sejak awal.</p><h4>Kata “mengekstrak” mengandaikan sesuatu yang tidak benar</h4><p>Bagian ini lebih halus, dan lebih mahal akibatnya.</p><p>Kata “ekstrak” menyiratkan bahwa sentimennya sudah ada di dalam teks dan tinggal diambil, seperti bijih dari dalam tanah. Kenyataannya tidak demikian, dan buktinya sederhana. Annotator manusia, seseorang yang menentukan valensi sebuah kalimat, sering kali tidak sepakat. Seandainya sentimen benar-benar merupakan properti intrinsik sebuah teks, mereka tidak akan berbeda pendapat.</p><p>Yang sebenarnya dilakukan sistem sentiment analysis adalah memprediksi label apa yang akan diberikan pembaca tertentu terhadap sebuah teks. Ia tidak sedang menemukan sesuatu yang tersembunyi, melainkan menebak putusan manusia.</p><p>Perbedaan ini terdengar filosofis sampai Anda mengerjakan proyek sungguhan. Jika kerangka berpikir Anda adalah ekstraksi, maka ketika akurasi mentok di 80 persen, Anda akan menyalahkan arsitektur. Anda akan menambah layer, mengganti model, dan memperbanyak data. Padahal 20 persen sisanya bisa jadi memang merupakan ketidaksepakatan manusia yang secara prinsip tidak dapat dipelajari.</p><p>Dengan kata lain, Anda sedang mengejar target yang tidak ada.</p><h4>Menelusuri asal katanya</h4><p>Kata <em>sentiment</em> berasal dari bahasa Latin <em>sentire</em>. Yang menarik, <em>sentire</em> tidak hanya berarti merasa, tetapi juga berpendapat. Kata <em>sentio</em> bisa diterjemahkan menjadi keduanya, tergantung konteks. Satu kata dengan dua fungsi, yang dalam bahasa modern justru kita pisahkan dengan rapi.</p><p>Turunannya cukup banyak, mulai dari <em>sense</em>, <em>sentient</em>, <em>consent</em>, <em>dissent</em>, sampai <em>resent</em>. Ada pula <em>sentence</em>, yang berasal dari <em>sententia</em>, artinya putusan yang diucapkan. Itulah sebabnya seorang hakim disebut menjatuhkan <em>sentence</em>. Jadi kata “kalimat” dan “sentimen” sesungguhnya bersaudara, dan leluhur bersama keduanya adalah penilaian yang dirasakan.</p><p>Makna modernnya terkunci pada abad kedelapan belas. Adam Smith menulis <em>The Theory of Moral Sentiments</em> pada tahun 1759, dan di sanalah kata <em>sentiment</em> menjadi istilah teknis untuk sesuatu yang sangat spesifik, yaitu perasaan yang membawa muatan evaluatif. Ia bukan <em>passion</em> yang mentah, dan bukan pula <em>reason</em> yang dingin, melainkan sesuatu di antara keduanya.</p><p>Dari sana muncul satu konsekuensi penting. Sentimen selalu tertuju pada sesuatu. Seseorang bisa merasa sedih tanpa tahu penyebabnya, tetapi tidak mungkin bersentimen negatif tanpa negatif terhadap apa.</p><p>Sentimen adalah relasi, bukan sesuatu yang utuh dengan dirinya sendiri. Dan relasi tidak bisa diekstrak. Jika sebuah relasi dipecah menjadi bagian-bagiannya, yang tersisa hanyalah kedua ujungnya, sedangkan relasinya sendiri justru hilang.</p><p>Kata <em>analysis</em> juga menyesatkan. Ia berasal dari bahasa Yunani, gabungan <em>ana-</em> dan <em>lysis</em>, yang berarti penguraian atau pelepasan simpul dan terpasang diam-diam di dalam kata sentiment analysis, mengasumsikan bahwa objeknya terdiri dari bagian-bagian, dan bahwa memahami berarti memecah.</p><p>Dalam praktiknya, yang benar-benar diurai bukanlah sentimennya, melainkan teksnya, dalam bentuk token, n-gram, atau embedding. Sentimennya sendiri justru dirakit secara utuh di ujung proses, menjadi satu label.</p><p>Jadi secara harfiah, sentiment analysis adalah analisis terhadap teks dan sintesis terhadap sentimen. Namanya salah menunjuk operasi mana yang sesungguhnya bersifat analitik. Nama yang salah menunjuk inilah sumber salah paham yang pertama.</p><h4>Definisi yang saya pakai</h4><blockquote>Sentiment analysis adalah proses menetapkan valensi, yaitu seberapa positif, negatif, atau netral, dari sikap yang diekspresikan sebuah teks terhadap objek yang dilihat dari sisi tertentu, menurut norma yang ditetapkan sebelumnya, oleh seorang annotator dengan peran tertentu.</blockquote><p>Definisi ini memang panjang, tetapi setiap frasa di dalamnya menjelaskan setiap komponen di dalam sentiment analysis.</p><p>Kata “menetapkan” dipakai menggantikan “mengekstrak”, karena tidak ada apa pun yang diambil.</p><p>Kata “valensi” dipakai menggantikan “emosi”, karena keduanya adalah task yang berbeda.</p><p>Frasa “diekspresikan sebuah teks” dipakai menggantikan “isi kepala penulis”, karena review palsu berbayar pun tetap keluar sebagai positif.</p><p>Frasa “dari sisi tertentu” diperlukan karena kuah soto yang dilihat dari sisi rasa dan kuah soto dilihat dari sisi kesehatan menghasilkan jawaban yang berbeda.</p><p>Frasa “norma yang ditetapkan sebelumnya” menegaskan bahwa norma bersifat prior, bukan temuan.</p><p>Dan kata “annotator” ada di sana karena selalu ada pembaca, dan perannya turut menentukan.</p><h4>Enam komponen</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/960/1*w90yYO1zXsptd1mW3Uf8-w.png" /></figure><p>Bagian dari sentimen terdiri dari empat hal, yaitu orang yang menulis, objek yang dilihat dari sisi tertentu, valensi yang merupakan keluaran akhirnya, dan kualitas berupa jenis perasaan yang dalam praktik sering tidak dipakai.</p><p>Sementara itu ada dua hal yang ditetapkan sebelum data dibaca, yaitu norma dan annotator.</p><p>Kedua kelompok ini tidak boleh dicampur, dan kesalahan itu berkali-kali saya lakukan sendiri ketika menyusun materinya.</p><p>Analogi yang paling membantu adalah kecepatan kereta. Ketika ada kereta berjalan, berapa kecepatannya? Relatif terhadap tanah, jawabannya 80 kilometer per jam. Relatif terhadap mobil yang berjalan beriringan dengan kecepatan sama, jawabannya nol.</p><p>Pertanyaan tentang berapa kecepatan sebenarnya, tanpa menyebut relatif terhadap apa, adalah pertanyaan yang rusak. Bukan sulit dijawab, melainkan belum menjadi pertanyaan.</p><p>Namun perhatikan apa yang tidak kita simpulkan dari situ. Kita tidak menyimpulkan bahwa kecepatan itu tidak ada, dan tidak pula menyimpulkan bahwa kecepatan bersifat subjektif. Begitu kerangka acuannya ditetapkan, jawabannya menjadi keras dan terukur. Orang yang menjawab 200 memang salah, bukan sekadar berbeda pendapat.</p><p>Kerangka acuan bukanlah bagian dari kecepatan, melainkan alat ukurnya. Norma dan annotator berperan persis seperti itu.</p><h4>Mengapa norma dan annotator tidak sama</h4><p>Dua contoh berikut mengunci perbedaan keduanya.</p><p>Kasus pertama menunjukkan norma yang berbeda dengan peran yang sama. Ambil kalimat “alpukat lemaknya banyak banget”. Seorang ahli gizi tahun 2026, yang memakai norma bahwa lemak tak jenuh tunggal menguntungkan, akan menilainya positif. Seorang ahli gizi tahun 1985, di puncak era low-fat, akan menilainya negatif. Keduanya sama-sama ahli gizi.</p><p>Kasus kedua menunjukkan peran yang berbeda dengan norma yang sama.</p><p>Normanya adalah:</p><blockquote>game yang baik itu yang bikin pemain puas dan mau lanjut main</blockquote><p>Kalimatnya adalah:</p><blockquote>“Bossnya sadis, mati 47 kali baru tembus”.</blockquote><p>Seorang pemain aktif di komunitas akan membacanya sebagai pujian terhadap tingkat kesulitan game terutama jika mereka penyuka soulslike, sehingga hasilnya positif. Seorang pembaca umum akan membacanya secara harfiah bahwa gamenya sulit dimainkan, sehingga hasilnya negatif.</p><p>Ringkasnya, norma menentukan dengan ukuran apa sesuatu dinilai, sedangkan peran menentukan lewat mata siapa teks itu dibaca.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/960/1*eTtF_zMk9bzd6qO_6vSe9A.png" /></figure><p>Di dalam tabel itu ada dua baris yang hasilnya tidak berubah antara pembaca A dan B, dan itu disengaja. Seandainya semua baris terbalik, akan ada orang yang menyimpulkan bahwa semuanya relatif. Dua baris itu berfungsi mencegah kesimpulan tersebut.</p><h4>Tambahan: satu norma tidak pernah cukup</h4><p>Sebelum masuk ke bagian teknis, ada satu demonstrasi singkat yang perlu ditampilkan.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/960/1*g2gZOUPAypRlSk0u1suVmg.png" /></figure><p>Setelannya sederhana. Normanya adalah bahwa barang yang baik itu murah, dan annotatornya adalah calon pembeli.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/960/1*YYJjCZynIsgEHyx0QkvJyQ.png" /></figure><p>Kita semua tentu tahu bahwa baris terakhir pada tabel tersebut seharusnya berlabel Negatif, namun dengan norma yang sudah ditentukan sebelumnya, kalimat “Barang murah, jelek” menghasilkan label Positif.</p><p>Mungkin sebagian besar orang tidak akan menerima label tersebut. Namun hasilnya benar menurut norma yang kita tetapkan sendiri. Normanya hanya mampu menilai harga, dan sama sekali tidak memiliki alat untuk mengomentari build quality.</p><p>Perbaikannya bukan mengganti model, melainkan menetapkan satu norma untuk setiap aspek. Pendekatan itulah yang disebut ABSA (Aspect-Based Sentiment Analysis).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/960/1*x_TsjCyomMf85OGngEX0xw.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/960/1*j0r1kFn2Ux4M-TA2Ni5ZCA.png" /></figure><p>Dua aspek yang kosong itu sering diisi keliru oleh kebanyakan orang. Teks ketiga tidak menyebut harga sama sekali, sehingga statusnya bukan netral melainkan tidak ada. Netral berarti aspeknya disebut tetapi tidak dinilai.</p><p>Hal ini sekaligus menjelaskan mengapa ABSA merupakan satu-satunya nama yang jujur di bidang ini. Sentiment analysis biasa menghasilkan satu label untuk keseluruhan teks, dan itu sebenarnya agregasi. ABSA benar-benar mengurai, dan menyebutkan objeknya secara eksplisit.</p><h3>Salah paham kedua: “Stopword aman dibuang”</h3><p>Mulai dari sini, semuanya bisa diukur.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/960/1*9WaXNwntzvENHxiViO8Vfw.png" /></figure><p>Sebelum melangkah lebih jauh, ada satu koreksi istilah yang perlu disampaikan. Pre-processing bukanlah NLP. Pre-processing adalah salah satu tahap di dalam NLP.</p><p>NLP sendiri adalah bidangnya, yang mencakup machine translation, question answering, speech recognition, chatbot, dan juga sentiment analysis.</p><p>Kalimat yang saya pakai untuk menguji adalah sebagai berikut.</p><blockquote>“Aku kepedasan bgt pas makan sambalnya Bu Indah di Warung Sederhana, rasanya memuaskan tapi tidak bagus untuk perut ya adik-adik”</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/960/1*DUegPYFgMSKkAhbCEs9BJw.png" /></figure><p>Pertanyaannya, siapa saja yang masuk ke dalam daftar stopword Sastrawi?</p><p>Kata <strong>di</strong>, <strong>yang</strong>, dan <strong>untuk </strong>memang ada di sana, dan itu wajar. Namun <strong>tapi </strong>juga ada di sana. Demikian pula <strong>tidak</strong>. Sementara itu <strong>gak </strong>justru tidak ada.</p><p>Kata <strong>tapi</strong> adalah konjungsi kontras yang menentukan klausa mana yang menang. Kata <strong>tidak </strong>adalah negasi yang membalik valensi. Keduanya merupakan penanda paling penting dalam bahasa apa pun, dan keduanya masuk ke daftar buang di dalam stopword.</p><p>Ketika pipeline dijalankan, hasilnya seperti ini.</p><blockquote><strong>21 token menjadi 16 token<br>yang dibuang: di, tapi, tidak, untuk, ya</strong></blockquote><p>Kalimat aslinya berbunyi “tidak bagus untuk perut”. Setelah langkah ini, yang tersisa hanyalah <strong>bagus</strong>.</p><p>Belum ada satu pun model yang dilatih, dan valensinya sudah terbalik.</p><p>Ada satu detail kecil yang sekaligus lucu dan mengganggu. Kata <strong>gak </strong>tidak ada di dalam daftar stopword, padahal artinya identik dengan <strong>tidak</strong>. Artinya, jika seorang reviewer mengetik &quot;gak enak&quot;, negasinya selamat. Jika ia mengetik &quot;tidak enak&quot;, negasinya hilang. Ejaan menentukan nasib valensi dari suatu kalimat.</p><h3>Salah paham ketiga: “Case folding bersifat netral”</h3><p>Bagian ini yang paling mudah diukur, dan hasilnya sangat rapi.</p><p>Saya melatih POS tagger dari korpus bawaan <strong>nlp-id</strong> yang berisi 559.872 token, dengan akurasi 99,24 persen pada 3.278 kalimat pengujian. Setelah itu saya menandai kalimat uji dua kali, yaitu sebelum dan sesudah hurufnya dikecilkan.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/544/1*EjGOe5QrxzJYykhw2layYw.png" /></figure><p>Empat tag berubah dari total 21 token, dan keempatnya adalah nama diri. Tujuh belas token lainnya sama sekali tidak tersentuh.</p><p>Jumlah NNP, yaitu penanda nama diri, turun dari empat menjadi nol.</p><p>Yang merepotkan adalah nasib <strong>Indah </strong>dan <strong>Sederhana</strong>. Keduanya kini dikenali sebagai kata sifat. Padahal di lexicon mana pun, kata sifat memiliki skor sentimen. Berapa angkanya akan kita lihat sebentar lagi.</p><p>Inilah alasan mengapa pada pipeline sungguhan, NER sering dijalankan sebelum case folding. Huruf kapital adalah petunjuk, dan langkah pertama pipeline membuangnya.</p><h4>Namun ada satu kejutan di sini</h4><p>Saya sempat menduga bahwa NER akan menunjukkan kerusakan yang sama dengan POS tagging. Ternyata dugaan itu keliru, dan saya rasa kekeliruannya justru lebih menarik daripada dugaan awalnya.</p><p>Ketika kalimat yang sama diproses dengan model <strong>cahya/bert-base-indonesian-NER</strong>, jumlah entitas yang terdeteksi sebelum dan sesudah case folding sama persis, yaitu dua entitas dengan hasil yang identik.</p><p>Penjelasannya ada pada tokenizer model tersebut. Keluarga model <strong>cahya/bert-base-indonesian </strong>bersifat uncased, sebagaimana dinyatakan sendiri di dalam kartu modelnya, yaitu tidak membedakan &quot;indonesia&quot; dari &quot;Indonesia&quot;. Tokenizer-nya melakukan lowercase secara internal, sehingga kedua masukan kita sudah menjadi teks yang sama persis sebelum model sempat melihatnya.</p><p>Dengan kata lain, case folding tidak merusak apa pun di sana, karena model tersebut memang tidak pernah memakai informasi kapital sejak awal.</p><p>Temuan ini justru memperkuat kesimpulan yang lebih besar, dan akan muncul lagi dua kali di dalam tulisan ini. Urutan langkah pre-processing bergantung sepenuhnya pada metode yang akan memakainya. Case folding menghapus empat tag NNP pada tagger yang dilatih dengan teks bercase, tetapi sama sekali tidak berpengaruh pada model uncased.</p><p>Ada pula satu catatan mengenai outputnya. Model itu menandai kata <strong>sambal </strong>sebagai entitas dengan keyakinan sekitar 0,56, dan <strong>indah </strong>sekitar 0,53. Angka keyakinan di bawah 0,6 tergolong rendah, dan <strong>sambal </strong>jelas bukan nama diri. Ini pengingat bahwa keluaran sebuah model perlu dibaca, bukan sekadar diterima.</p><h3>Salah paham keempat: “Kata beruang menjadi uang”</h3><p>Contoh ini muncul di sangat banyak tutorial sentiment analysis berbahasa Indonesia sebagai ilustrasi over-stemming.</p><p>Saya menjalankannya dengan Sastrawi versi terbaru.</p><blockquote>beruang menjadi beruang</blockquote><p>Tidak terjadi.</p><p>Jika contoh tersebut ada di dalam materi ajar, slide, atau bab metodologi skripsi Anda, sebaiknya diganti. Bukan karena over-stemming tidak nyata (sebab persoalan itu sangat nyata), melainkan karena contoh spesifik ini sudah tidak dapat direproduksi.</p><p>Yang benar-benar terjadi adalah sebagai berikut.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/564/1*0YKIwQoFgY27HD7SOKms5Q.png" /></figure><h3>Salah paham kelima: “Lemmatization mengatasi over-stemming”</h3><p>Keyakinan ini setengah benar, dan setengah yang lain justru bagian yang penting.</p><p>Perhatikan kolom lemma pada tabel di atas. Kata <strong>pengunjung </strong>dan <strong>memuaskan</strong> berhasil diselamatkan. Stemming menghasilkan potongan yang bukan kata, yaitu <strong>unjung </strong>dan <strong>muas</strong>, sedangkan lemmatization menghasilkan kata sungguhan, yaitu <strong>kunjung </strong>dan <strong>puas</strong>.</p><p>Perbedaan ini penting untuk sentimen. Kata <strong>muas </strong>tidak ada di lexicon mana pun, sehingga nilai <strong>memuaskan </strong>hilang sepenuhnya. Sedangkan <strong>puas </strong>ada di dalam lexicon.</p><p>Namun perhatikan baris-baris berikutnya.</p><p>Kata <strong>pengalaman </strong>menjadi <strong>alam </strong>di kedua metode. Keduanya sama-sama keliru.</p><p>Tiga baris berimbuhan <strong>ke-…-an</strong> juga menghasilkan keluaran yang sama di kedua metode. Imbuhan itu berarti berlebihan, dan di situlah muatan negatifnya berada. Kata <strong>kepedasan </strong>yang merupakan keluhan berubah menjadi <strong>pedas </strong>yang bersifat deskriptif, bahkan bisa dinilai positif oleh sebagian orang.</p><p>Lemmatization memang lebih rapi, tetapi lemmatization bukan solusi yang selalu bisa diandalkan.</p><h4>Aturan praktis dari bagian ini</h4><blockquote>Semakin banyak yang Anda buang saat pre-processing, semakin besar kemungkinan Anda ikut membuang sentimennya.</blockquote><p>Pada tugas NLP lain, kata seperti <strong>tidak </strong>dan <strong>tapi </strong>memang layak dibuang karena tidak memberi tahu apa pun tentang topik sebuah teks. Pada sentiment analysis, justru kata-kata itulah yang menentukan jawabannya.</p><p>Membuang stopword ikut membuang <strong>tidak</strong>. Case folding menghilangkan nama diri sekaligus penekanan berupa huruf kapital. Normalisasi mengubah <strong>bagusss </strong>menjadi <strong>bagus</strong>, sehingga derajatnya hilang. Menghapus tanda baca ikut membuang emoji, padahal pada data berbahasa Indonesia, emoji sering menjadi satu-satunya penanda sarkasme.</p><p>Dan yang paling penting untuk menutup bagian ini adalah kesimpulan berikut.</p><blockquote>Tidak satu pun dari tujuh langkah pre-processing yang mampu memutuskan sebuah teks itu positif atau negatif. Semuanya hanya menyiapkan bahan, dan sebagian justru membuang bahannya.</blockquote><p>Lalu valensinya datang dari mana?</p><h3>Salah paham keenam: “Lexicon sudah divalidasi, tinggal dipakai”</h3><p>Jawaban pertama mengenai “Lalu valensinya datang dari mana?” yang paling masuk akal adalah menuliskan kamusnya. Beri skor pada setiap kata, lalu jumlahkan.</p><p>Untuk bahasa Indonesia, kamus yang paling banyak dipakai adalah InSet.</p><blockquote>Fajri Koto dan Gemala Y. Rahmaningtyas, “InSet Lexicon: Evaluation of a Word List for Indonesian Sentiment Analysis in Microblogs”, IEEE, 21st International Conference on Asian Language Processing (IALP), Singapura, December 2017. Repositori: <a href="http://github.com/fajri91/InSet">github.com/fajri91/InSet</a></blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*7TOGFskruxNIfqB4GMf28w.png" /></figure><p>Isinya 3.609 kata positif dan 6.607 kata negatif yang berhasil terbaca, dengan bobot dari -5 sampai +5.</p><h4>Temuan pertama: kata netral mendapat skor</h4><p>Saya menjumlahkan skor InSet untuk kalimat uji tadi dalam tiga kondisi.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/960/1*HRVQZs1pVsHwvx9PCklHlw.png" /></figure><p>Sekarang perhatikan kata mana saja yang menyumbang skor pada baris pertama.</p><blockquote>aku+1 banget+1 pas+3 indah+1 sederhana-2 tidak-5 bagus-2 perut+3 ya+4</blockquote><p>Kata <strong>pas </strong>bernilai +3, <strong>perut </strong>bernilai +3, <strong>ya </strong>bernilai +4, dan <strong>aku </strong>bernilai +1.</p><p>Di dalam kalimat kita, tidak satu pun dari keempatnya merupakan kata bersentimen. Kata <strong>perut </strong>di situ adalah organ tubuh. Kata <strong>pas </strong>berfungsi sebagai keterangan waktu. Kata <strong>ya </strong>adalah partikel sapaan. Keempat kata netral tersebut menyumbang total +11.</p><p>Selain itu ada <strong>indah </strong>yang bernilai +1 dan <strong>sederhana </strong>yang bernilai -2, padahal yang satu nama orang dan yang satu lagi nama warung. Lexicon tidak dapat membedakannya, karena lexicon tidak memiliki slot untuk mencatat sebuah kata merujuk pada apa.</p><h4>Temuan kedua</h4><p>Secara logika, sebuah kata seharusnya hanya berada di salah satu daftar. Saya menghitung berapa banyak yang berada di kedua-duanya.</p><p>Hasilnya 1.142 kata, atau sekitar sepertiga dari seluruh daftar positif.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/854/1*OxPbzwoURWBlLoDzaqzOyg.png" /></figure><p>Dengan kata lain, lexicon sentimen bahasa Indonesia yang paling banyak dikutip di skripsi dan publikasi ilmiah tidak sepakat dengan dirinya sendiri mengenai apakah <strong>bagus </strong>itu kata positif atau negatif.</p><p>Kasus <strong>memuaskan </strong>bahkan persis nol, karena +5 dan -5 saling meniadakan.</p><p>Ini bukan bug, dan juga bukan alasan untuk membuang InSet. Ini adalah jejak dari sesuatu yang lebih mendasar. Lexicon dibangun dari anotasi manusia, dan manusianya memang tidak sepakat. Ketidaksepakatan itu tidak hilang ketika dibekukan menjadi berkas. Ia hanya berhenti terlihat.</p><p>Simpan baik-baik poin ini, karena ia yang akan menutup seluruh tulisan.</p><h4>Di mana penjumlahan itu mulai gagal</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/960/1*0ZnehuxO0VUIRiPAeQSz1w.png" /></figure><p>Dua baris memberikan hasil keliru. Namun yang paling menarik justru dua baris pertama yang hasilnya benar.</p><p>Kalimat “tidak enak” dan “tidak bagus” sama-sama keluar sebagai Negatif, dan itu memang tepat. Persoalannya, ketepatan itu kebetulan. Penjumlahan tidak tahu apa itu negasi. Ia hanya menjumlahkan angka, dan kebetulan <strong>tidak </strong>bernilai -5 yang cukup besar untuk mendominasi.</p><p>Begitu kata sentimennya bernilai positif, seperti pada “tidak mengecewakan”, kebetulan itu habis.</p><h3>Salah paham ketujuh: “Kalau kurang akurat, tambahkan aturan”</h3><p>Jawaban ini masuk akal, dan memang begitulah sejarahnya. Lexicon ditambah tumpukan aturan menghasilkan pendekatan yang disebut rule-based.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/960/1*y8t2kKIilPJWo-gdZKY9kw.png" /></figure><p>Aturannya ada tiga. Kata <strong>tidak </strong>membalik tanda kata di sebelah kanannya. Kata <strong>banget </strong>mengalikan nilai kata di sebelah kirinya sebanyak dua. Kata <strong>tapi </strong>membuang klausa di sebelah kiri, sehingga klausa kanan yang menang.</p><p>Ketiga kata itu tidak ikut dijumlahkan, karena mereka bukan nilai melainkan operator. Cara kerjanya, potong dulu jika ada <strong>tapi</strong>, ambil skor setiap token. Periksa tetangga kiri untuk pembalik. Periksa tetangga kanan untuk pengali. Lalu jumlahkan.</p><blockquote>“tidak mengecewakan”<br> <strong>tidak </strong>adalah sebuah<strong> </strong>operator, dilewati<br> mengecewakan skor −3<br> tetangga kiri adalah<strong> ‘tidak’</strong>, sehingga −3 dikali −1 menjadi +3<br> total = +3</blockquote><p>Sekarang kelima kalimat tadi dijalankan ulang dengan aturan tersebut.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*xaZOwV3QjA4_nqqzYRHMWw.png" /></figure><p>Dua baris membaik, tetapi satu baris justru memburuk.</p><p>Baris “tidak bagus” adalah temuan terpenting di bagian ini. Sebelum aturan dipasang, hasilnya Negatif dan itu benar. Setelah aturan pembalik dipasang, hasilnya berubah menjadi Positif dan itu salah.</p><p>Penyebabnya adalah nilai <strong>bagus </strong>di InSet yang ternyata -2. Aturan pembalik bekerja persis seperti yang dirancang, yaitu membalik tanda. Persoalannya, tanda yang dibalik itu memang sudah keliru sejak awal.</p><blockquote>Aturan tidak menambal lexicon. Aturan justru memperkuat apa pun yang ada di dalam lexicon, termasuk kesalahannya.</blockquote><h4>Tiga kalimat yang menggagalkan seluruh aturan</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/960/1*Aa2L4hwPOlfInTgcqBT3rw.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*yfg5gftaNah07hmrMsjg_Q.png" /></figure><p>Ketiganya bukan contoh aturan yang gagal bekerja, melainkan contoh aturan yang bekerja dengan baik lalu menghasilkan jawaban keliru dengan penuh keyakinan.</p><p>Kasus pertama paling instruktif. Kata <strong>terlalu </strong>bernilai -3 di InSet. Karena posisinya tepat di sebelah kanan <strong>tidak</strong>, aturan pembalik menyerangnya dan mengubahnya menjadi +3. Sementara itu <strong>enak </strong>yang bernilai +1 lolos tanpa dibalik sama sekali. Totalnya menjadi +4, yaitu positif kuat, untuk kalimat yang jelas merupakan keluhan.</p><p>Kasus kedua bukan soal dua operator yang bertumpuk, melainkan dua operator yang saling memakan. Aturan <strong>tapi </strong>dijalankan lebih dulu dan menghapus klausa tempat <strong>tidak </strong>berada.</p><p>Sebagai catatan tambahan, kata <strong>mahal </strong>bernilai +1 di InSet. Barang mahal dinilai positif.</p><h4>Pola yang muncul</h4><p>Setiap kali kita menambal, muncul kasus baru yang membutuhkan tambalan berikutnya. Dan tambalan itu dapat merusak kasus yang sebelumnya sudah benar.</p><p>Jadi pertanyaannya bukan berapa aturan lagi yang kurang, melainkan apakah aturan bisa ditulis sampai selesai.</p><p>Jawabannya tidak. Dan di titik itulah machine learning masuk.</p><h4>Machine learning, dan ke mana normanya pergi</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/960/1*Krt37rUxs7eDdM9Qn8e3aQ.png" /></figure><p>Kita berhenti menulis aturan. Sebagai gantinya, kita berikan contoh yang sudah dilabeli, lalu membiarkan mesin mencari polanya sendiri.</p><p>Namun yang paling penting bukan itu, melainkan apa yang berubah.</p><blockquote>Norma tidak hilang. Norma berpindah, dari berkas yang bisa kita buka menjadi bobot yang tidak bisa kita baca.</blockquote><p>Rantainya terdiri dari empat langkah, yaitu norma, guideline, annotator yang memberi label, pelatihan model, dan akhirnya bobot.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*NfVx8LGV4_Jrk2yYUXGrpg.png" /></figure><p>Annotator tidak hilang dari proses ini. Posisinya hanya bergeser ke tahap yang lebih awal. Ia tidak lagi menilai teks satu per satu ketika model dipakai, melainkan menilai ribuan teks lebih dulu untuk dijadikan data pelatihan.</p><p>Dari situ rantainya menjadi jelas. Guideline menentukan bagaimana annotator melabeli, label menentukan apa yang dipelajari model, dan hasil pelajaran itulah yang tersimpan sebagai bobot. Artinya guideline yang ditulis di awal proyek ikut menentukan isi model yang jadi di akhir, meskipun tidak ada satu pun kalimatnya yang tersisa di sana.</p><h4>Embedding, LSTM, dan Transformer</h4><p>Pola yang sama terus berlanjut. Setiap tahap lahir dari kegagalan tahap sebelumnya.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/960/1*npgtUTCCHod2cpN15SmaVw.png" /></figure><p>Ada satu catatan kejujuran mengenai LSTM. Yang dipakai orang untuk klasifikasi teks hampir selalu BiLSTM, yaitu dua LSTM yang membaca dari kiri dan dari kanan. Jadi konteks kanan sebenarnya tidak benar-benar hilang. Masalah aslinya bukan arah, melainkan jarak. Konteks disimpan di dalam satu vektor berukuran tetap yang diperbarui token demi token, sehingga semakin jauh jaraknya semakin luntur informasinya.</p><p>Transformer menawarkan dua hal. Pertama, jarak tidak lagi berpengaruh, karena kata pertama dan kata kelima puluh terhubung langsung dalam satu langkah, bukan empat puluh sembilan langkah. Kedua, prosesnya dapat diparalelkan, dan itulah yang membuat pretraining di miliaran kata menjadi mungkin.</p><h4>IndoBERT, dan satu ironi yang menutup lingkaran</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/960/1*GTxa0eQja2a7S8exAp3KRw.png" /></figure><p>IndoBERT dilatih khusus untuk bahasa Indonesia, sudah memahami morfologi, dan tinggal di-fine-tune dengan data yang jauh lebih sedikit.</p><p>Di sinilah muncul ironi yang layak disebut. Stemming dan lemmatization justru tidak diperlukan. Subword tokenization sudah menangkap bahwa <strong>mengecewakan </strong>berkerabat dengan <strong>kecewa</strong>. Apa yang wajib di lexicon ternyata merusak di BERT.</p><p>Ini bukan kontradiksi, melainkan pola yang sudah muncul tiga kali di dalam tulisan ini. Keputusan pre-processing bergantung sepenuhnya pada metode yang akan kita gunakan.</p><p>Ada dua catatan praktis yang perlu disampaikan.</p><p>Pertama, IndoBERT bersifat uncased. Tokenizer-nya melakukan lowercase secara otomatis. Artinya argumen mengenai kapital dan NER tadi tidak berlaku di pipeline IndoBERT, bukan karena diabaikan, melainkan karena modelnya memang tidak pernah dilatih dengan huruf kapital. Inilah yang menjelaskan hasil pengujian NER yang sudah kita lihat sebelumnya.</p><p>Kedua, stopword sebaiknya jangan dibuang. Bagian ini yang sering keliru. BERT dilatih dengan kalimat utuh, sehingga menghapus stopword akan menghasilkan masukan yang tidak menyerupai data pretraining-nya. Lebih spesifik lagi, kata <strong>tidak</strong>, <strong>tapi</strong>, dan <strong>bukan </strong>justru merupakan kata yang dipakai attention untuk menghitung konteks. Membuangnya berarti membuang bahan mentah dari mekanisme yang menjadi alasan orang memilih BERT sejak awal.</p><p>Jadi stopword removal keliru di kedua metode. Di lexicon, negasinya hilang. Di BERT, masukannya keluar dari distribusi. Tidak ada satu pun metode sentiment analysis di mana membuang kata <strong>tidak </strong>merupakan tindakan yang benar.</p><h3>Salah paham kedelapan: “Akurasi tinggi berarti modelnya bagus”</h3><p>Untuk mengukur akurasi, kita perlu jawaban yang dianggap benar sebagai pembanding. Jawaban itu tidak turun dari langit. Ia dibuat oleh annotator, yaitu orang yang melabeli data pelatihan satu per satu.</p><p>Jadi ketika sebuah model disebut berakurasi 85 persen, artinya bukan model itu benar 85 persen. Artinya model itu <strong>sepakat dengan annotator</strong> pada 85 persen kasus.</p><p>Perbedaan ini terdengar sepele sampai Anda menanyakan satu hal berikutnya. Bagaimana kalau annotator-nya sendiri tidak sepakat?</p><h4>Ketika manusianya sendiri berbeda pendapat</h4><p>Bayangkan dua orang diminta melabeli seratus review yang sama. Ternyata pada dua puluh review, keduanya memberi label berbeda. Bukan karena salah satunya ceroboh, melainkan karena review-review itu memang bisa dibaca dua arah, persis seperti kalimat soto di awal tulisan ini.</p><p>Dalam situasi seperti itu, tidak ada model yang bisa benar lebih dari delapan puluh persen. Bukan karena modelnya kurang canggih, melainkan karena dua puluh persen sisanya memang tidak punya jawaban tunggal.</p><p>Menambah layer tidak akan menolong. Mengganti arsitektur juga tidak. Anda sedang mengejar target yang tidak ada.</p><h4>Cara mengukurnya</h4><p>Ada angka untuk hal ini, namanya <strong>tingkat kesepakatan antar-annotator</strong>. Cara menghitungnya sederhana, yaitu minta dua orang melabeli sebagian data yang sama, lalu hitung berapa persen label mereka cocok.</p><p>Ukuran yang paling sering dipakai untuk ini disebut Cohen’s kappa. Bedanya dengan persentase biasa, kappa sudah memperhitungkan kesepakatan yang terjadi karena kebetulan. Kalau dua orang asal menebak positif dan negatif, mereka akan cocok sekitar lima puluh persen tanpa berpikir sama sekali. Kappa mengoreksi hal itu, sehingga nilainya nol berarti tidak lebih baik daripada menebak.</p><p>Yang perlu Anda ingat cukup satu hal. Angka ini diperiksa <strong>sebelum</strong> melatih model, bukan sesudahnya.</p><h4>Kombinasi yang mencurigakan</h4><p>Kalau kesepakatan antar-annotator rendah tetapi akurasi model tinggi, itu bukan kabar baik.</p><p>Kemungkinan besar model Anda sedang menghafal kebiasaan satu orang annotator tertentu, bukan mempelajari pola sentimen yang sesungguhnya. Angkanya bagus di atas kertas, tetapi tidak akan bertahan begitu bertemu data baru.</p><h4>Mengapa model sering ambruk di data lain</h4><p>Hal yang sama menjelaskan kejadian yang sering dialami banyak orang. Sebuah model dilatih dengan review produk Tokopedia, hasilnya bagus, lalu dipakai untuk komentar YouTube dan tiba-tiba akurasinya jatuh.</p><p>Di literatur, gejala ini disebut <em>domain dependence</em>. Namun penyebabnya sebenarnya lebih spesifik daripada sekadar berganti domain.</p><p>Model itu dilatih untuk meniru cara pembeli menilai produk. Ketika Anda memakainya pada komentar YouTube, Anda meminta model itu menilai sesuatu yang standarnya berbeda. Kata “sadis” di kolom komentar game bisa berarti pujian, sedangkan di review produk berarti keluhan.</p><p>Jadi bukan modelnya yang kurang pintar. Alat ukurnya sedang dipakai di tempat yang bukan asalnya.</p><h4>Pemeriksaan yang bisa Anda lakukan</h4><p>Cara memeriksanya sederhana dan sama sekali tidak memerlukan model. Ambil lima puluh data, minta dua orang melabelinya secara terpisah, lalu bandingkan hasilnya. Kalau mereka sering berbeda, yang perlu diperbaiki adalah guideline-nya, bukan modelnya.</p><p>Dan itu pekerjaan yang jauh lebih murah daripada melatih ulang model tiga kali sambil menebak-nebak apa yang salah.</p><h3>Jadi, mengapa banyak orang salah paham?</h3><p>Delapan salah paham tadi tampak tersebar. Sebagian menyangkut istilah, sebagian menyangkut cara kerja alat, dan sebagian lagi menyangkut cara membaca angka. Namun sesungguhnya semuanya berasal dari satu sebab yang sama.</p><p>Ingat kembali dua hal yang harus ditetapkan sebelum data dibaca, yaitu <strong>norma</strong> dan <strong>annotator</strong>. Norma adalah pernyataan tentang apa yang dianggap baik dan buruk, misalnya “makanan yang baik itu yang enak” atau “makanan yang baik itu yang tidak merusak badan”. Annotator adalah orang yang menerapkan norma tersebut ketika melabeli data.</p><p>Keduanya selalu ada di dalam setiap sistem sentiment analysis. Yang berubah dari satu metode ke metode berikutnya hanyalah seberapa mudah keduanya dilihat.</p><p><strong>Dan setiap kali alatnya menjadi lebih canggih, keduanya menjadi lebih tersembunyi.</strong></p><h4>Empat tahap, semakin lama semakin gelap</h4><p>Pada tahap <strong>lexicon</strong>, normanya masih berupa berkas biasa. Kata <strong>bagus </strong>bernilai +2 di satu berkas dan −4 di berkas lain. Anda bisa membuka berkas itu, membacanya baris demi baris, dan tidak setuju dengan isinya. Ketidaksepakatan manusia yang membuatnya masih terlihat telanjang di sana.</p><p>Pada tahap <strong>rule-based</strong>, normanya masih bisa dibaca, tetapi sekarang bercampur dengan aturan-aturan yang saling memengaruhi. Ketika sebuah kalimat keluar sebagai positif, Anda tidak lagi bisa langsung menunjuk penyebabnya. Kita sudah melihat contohnya, ketika aturan <strong>tapi </strong>menghapus klausa tempat kata <strong>tidak </strong>berada.</p><p>Pada tahap <strong>machine learning</strong>, normanya sudah berubah wujud menjadi angka-angka di dalam model, yang biasa disebut bobot. Jumlahnya jutaan, dan tidak ada satu pun yang bisa dibaca sebagai kalimat. Tidak ada berkas yang bisa dibuka lagi. Yang tersisa hanyalah satu angka akurasi di ujung proses.</p><p>Pada tahap <strong>pretrained model</strong> seperti IndoBERT, normanya bahkan bukan lagi milik Anda. Norma itu sudah ditetapkan oleh annotator yang membuat dataset-nya, mengikuti guideline yang tidak pernah Anda baca, dan disusun untuk pembaca yang mungkin berbeda dari pembaca Anda.</p><p>Empat baris kode, dan enam keputusan sudah diambil untuk Anda oleh orang yang tidak Anda kenal.</p><h4>Mengapa hal ini luput dari perhatian</h4><p>Salah pahamnya bukan karena orang malas atau tidak teliti. Salah pahamnya justru karena alatnya bekerja terlalu mulus.</p><p>Tidak ada pesan error. Tidak ada peringatan. Perintah pipeline(&quot;sentiment-analysis&quot;) berjalan dengan lancar, angkanya keluar dalam hitungan detik, dan angkanya tampak wajar. Tidak ada apa pun yang memberi tahu Anda bahwa ada enam keputusan yang sudah diambil di belakang layar.</p><p>Bandingkan dengan kesalahan pemrograman biasa. Kalau kode Anda salah ketik, Python akan berhenti dan memberi tahu di baris berapa. Kalau guideline Anda salah, tidak ada yang berhenti. Semuanya tetap berjalan, dan hasilnya tetap keluar.</p><p>Ditambah lagi, namanya sendiri sudah menyesatkan sejak awal, seperti yang kita telusuri di bagian pertama. Kata “analysis” berarti menguraikan sesuatu menjadi bagian-bagiannya, padahal yang sebenarnya terjadi adalah kebalikannya, yaitu merangkai banyak hal menjadi satu label. Kata “sentiment” dibaca kebanyakan orang sebagai “emosi”, padahal keduanya berbeda. Dan kata “ekstraksi” mengesankan ada sesuatu yang tinggal diambil dari dalam teks, padahal yang dicari itu tidak pernah ada di sana.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/960/1*hx6p6i28amEFa0RfhI5DKg.png" /></figure><p>Enam komponen di bagian awal tadi bukan teori. Keenamnya yang menentukan apakah angka akurasi Anda berarti sesuatu atau tidak.</p><p>Dan ketika nanti Anda memuat IndoBERT dalam empat baris kode, ingatlah bahwa Anda tidak sedang menghindari keenam komponen itu. Anda sedang mewarisi keputusan orang lain mengenai keenamnya.</p><h3>Silakan periksa sendiri</h3><p>Semua angka di dalam tulisan ini dapat Anda jalankan ulang. Notebook-nya tersedia di sini.</p><p><a href="https://drive.google.com/drive/folders/1-99UyQkjgo4eYatrspPbkrf-pi-wWVW7?usp=sharing"><strong>Google Colab: “Mengapa Banyak Orang Salah Paham tentang Sentiment Analysis”</strong></a></p><p>Isinya sembilan sel dan memakan waktu sekitar lima menit. Di dalamnya termasuk pengujian kata <strong>beruang</strong>, penghitungan 1.142 kata yang tumpang tindih di InSet, tabel rule-based beserta baris yang memburuk, dan pemeriksaan tokenizer yang membuktikan bahwa model NER-nya memang bersifat uncased.</p><p>Jika ada yang meleset dari apa yang saya tulis, saya ingin mengetahuinya.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/632/1*n5UgjIRyblgyQHXbKpdYdQ.png" /></figure><p>Seluruh perangkat di atas berlisensi terbuka dan boleh dipakai untuk keperluan riset maupun publikasi, selama sumbernya disebutkan.</p><h3>Bacaan lanjutan</h3><p>Argumen inti tulisan ini, yaitu bahwa ketidaksepakatan annotator merupakan sinyal dan bukan noise, memiliki literaturnya sendiri yang jauh lebih tua dari saya.</p><ul><li>Lora Aroyo dan Chris Welty, “Truth Is a Lie: Crowd Truth and the Seven Myths of Human Annotation”, AI Magazine, 2015</li><li>Barbara Plank, “The ‘Problem’ of Human Label Variation: On Ground Truth in Data, Modeling and Evaluation”, EMNLP, 2022</li><li>Adam Smith, <em>The Theory of Moral Sentiments</em>, 1759, untuk asal-usul konsep <em>impartial spectator</em></li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/960/1*Gom-Q4ra8Jn0sgDsxNsymQ.png" /></figure><p>Ditulis sebagai pendamping materi Data Science 2025/2026, UKM Amikom Computer Club.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=05f317058f42" width="1" height="1" alt=""><hr><p><a href="https://medium.com/amcc-amikom/mengapa-banyak-orang-salah-paham-tentang-sentiment-analysis-05f317058f42">Mengapa Banyak Orang Salah Paham tentang Sentiment Analysis?</a> was originally published in <a href="https://medium.com/amcc-amikom">Amikom Computer Club</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[From Sensor to Long Range: Pengiriman Data IoT Jarak Jauh Menggunakan LoRa dan LoRaWAN dengan…]]></title>
            <link>https://medium.com/amcc-amikom/from-sensor-to-long-range-pengiriman-data-iot-jarak-jauh-menggunakan-lora-dan-lorawan-dengan-12a0d624f1e8?source=rss----fe834c257357---4</link>
            <guid isPermaLink="false">https://medium.com/p/12a0d624f1e8</guid>
            <category><![CDATA[software]]></category>
            <category><![CDATA[hardware]]></category>
            <category><![CDATA[hardware-software]]></category>
            <dc:creator><![CDATA[Nibras Ahmad]]></dc:creator>
            <pubDate>Fri, 26 Jun 2026 12:26:37 GMT</pubDate>
            <atom:updated>2026-06-26T12:26:35.906Z</atom:updated>
            <content:encoded><![CDATA[<h3>From Sensor to Long Range: Pengiriman Data IoT Jarak Jauh Menggunakan LoRa dan LoRaWAN dengan Simulasi Wokwi</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*0iKemE7txSHkg-TlpVKF0Q.png" /></figure><h3>Halo Sobat IoT Application/Dashboard Simulasi</h3><p>Selamat datang kembali di <strong>Class Divisi Internet of Things AMCC</strong>.</p><p>Setelah sebelumnya kita belajar mengenai Pengaplikasian Protokol BLE(Bluetooth Low Energy) pada pertemuan kali ini kita akan masuk ke materi yang tidak kalah menarik, yaitu <strong>komunikasi IoT jarak jauh menggunakan LoRa dan LoRaWAN</strong>.</p><p>Mungkin teman-teman pernah bertanya:</p><blockquote><em>“Kalau sensor dipasang di sawah, kebun, gunung, tambak, atau tempat yang tidak ada Wi-Fi, datanya dikirim pakai apa?”</em></blockquote><p>Nah, salah satu jawabannya adalah <strong>LoRa</strong>.</p><p>LoRa sering digunakan pada sistem IoT yang membutuhkan komunikasi jarak jauh, hemat daya, dan tidak harus mengirim data berukuran besar. Contohnya seperti monitoring suhu, kelembapan tanah, ketinggian air, kualitas udara, hingga sistem smart farming.</p><p>Pada materi ini, kita akan belajar konsep dasarnya dulu melalui <strong>simulasi Wokwi</strong>, lalu memahami bagaimana alur data dari sensor bisa dikirim menggunakan konsep LoRa dan diteruskan melalui arsitektur LoRaWAN.</p><h3>Apa yang Akan Kita Pelajari?</h3><p>Pada pertemuan ini, teman-teman akan mempelajari beberapa hal berikut:</p><ol><li>Konsep dasar LPWAN.</li><li>Perbedaan LoRa dan LoRaWAN.</li><li>Cara kerja komunikasi sensor jarak jauh.</li><li>Fungsi gateway pada LoRaWAN.</li><li>Simulasi pengiriman data sensor menggunakan Wokwi.</li><li>Contoh penerapan LoRa pada dunia nyata.</li></ol><h3>Apa itu LPWAN?</h3><p>Sebelum masuk ke LoRa, kita perlu mengenal dulu istilah <strong>LPWAN</strong>.</p><p><strong>LPWAN</strong> adalah singkatan dari <strong>Low Power Wide Area Network</strong>.</p><p>Kalau dijelaskan secara sederhana, LPWAN adalah jenis jaringan komunikasi yang dirancang untuk perangkat IoT agar bisa:</p><ul><li>mengirim data dari jarak jauh,</li><li>menggunakan daya listrik yang rendah,</li><li>cocok untuk perangkat berbasis baterai,</li><li>dan mengirim data kecil secara berkala.</li></ul><p>Jadi, LPWAN ini bukan dibuat untuk streaming video, download file besar, atau komunikasi cepat seperti Wi-Fi. LPWAN lebih cocok untuk data kecil seperti:</p><pre>Suhu: 28°C<br>Kelembapan: 70%<br>Status air: Aman<br>Gas: Normal</pre><p>Karena datanya kecil, perangkat bisa lebih hemat energi dan mampu bekerja dalam waktu lama.</p><h3>Apa itu LoRa?</h3><p><strong>LoRa</strong> adalah teknologi komunikasi radio jarak jauh yang banyak digunakan pada perangkat IoT.</p><p>LoRa memungkinkan perangkat seperti ESP32, Arduino, atau sensor node untuk mengirim data ke perangkat penerima lain dengan jarak yang lebih jauh dibanding komunikasi Wi-Fi biasa.</p><p>Contohnya, sebuah sensor suhu di area pertanian bisa mengirim data ke gateway yang berada di lokasi lain tanpa harus terhubung ke Wi-Fi.</p><p>Secara sederhana, alurnya seperti ini:</p><pre>Sensor<br>↓<br>Microcontroller<br>↓<br>Modul LoRa<br>↓<br>Penerima LoRa / Gateway</pre><p>LoRa cocok digunakan ketika perangkat berada di lokasi yang:</p><ul><li>jauh dari router Wi-Fi,</li><li>minim jaringan internet,</li><li>membutuhkan konsumsi daya rendah,</li><li>dan hanya mengirim data sensor secara berkala.</li></ul><h3>Apa itu LoRaWAN?</h3><p>Kalau LoRa adalah teknologi komunikasi radionya, maka <strong>LoRaWAN</strong> adalah protokol atau aturan jaringan yang mengatur bagaimana perangkat LoRa berkomunikasi dalam sistem IoT yang lebih besar.</p><p>LoRaWAN biasanya memiliki beberapa bagian utama:</p><pre>End Device<br>↓<br>Gateway<br>↓<br>Network Server<br>↓<br>Application Server / Dashboard</pre><p>Penjelasannya seperti ini:</p><ul><li><strong>End Device</strong> adalah perangkat sensor, misalnya ESP32 + DHT22 + modul LoRa.</li><li><strong>Gateway</strong> adalah perangkat penerima yang menangkap sinyal LoRa dari end device.</li><li><strong>Network Server</strong> bertugas mengatur data yang masuk, validasi perangkat, dan routing data.</li><li><strong>Application Server</strong> adalah tempat data ditampilkan, misalnya dashboard monitoring.</li></ul><p>Jadi, LoRaWAN membuat komunikasi LoRa menjadi lebih terstruktur dan cocok digunakan untuk banyak perangkat IoT sekaligus.</p><h3>Perbedaan LoRa dan LoRaWAN</h3><p>Agar lebih mudah dipahami, kita lihat perbedaannya dalam tabel berikut:</p><p>BagianPenjelasanLoRaTeknologi komunikasi radio jarak jauhLoRaWANProtokol jaringan yang menggunakan LoRaLoRaBisa digunakan point-to-point antar perangkatLoRaWANMenggunakan gateway dan serverLoRaCocok untuk eksperimen sederhanaLoRaWANCocok untuk sistem IoT skala besar</p><p>Contoh sederhananya:</p><p>Kalau LoRa itu seperti <strong>bahasa komunikasi radionya</strong>, maka LoRaWAN adalah <strong>aturan lengkap bagaimana pesan itu dikirim, diterima, diproses, dan diteruskan ke aplikasi</strong>.</p><h3>Kenapa Kita Belajar LoRa?</h3><p>Karena tidak semua perangkat IoT bisa dipasang di tempat yang dekat dengan Wi-Fi.</p><p>Bayangkan kita ingin membuat sistem monitoring di area seperti:</p><ul><li>sawah,</li><li>tambak,</li><li>perkebunan,</li><li>sungai,</li><li>hutan,</li><li>gudang besar,</li><li>atau daerah terpencil.</li></ul><p>Kalau menggunakan Wi-Fi, jangkauannya terbatas. Kalau menggunakan jaringan seluler, biaya dan konsumsi dayanya bisa lebih besar.</p><p>Di sinilah LoRa menjadi solusi yang menarik.</p><p>LoRa cocok digunakan untuk sistem yang membutuhkan pengiriman data kecil, jarak jauh, dan hemat daya.</p><h3>Studi Kasus: Monitoring Suhu dan Kelembapan Area Pertanian</h3><p>Pada praktik kali ini, kita akan membuat simulasi sederhana dengan studi kasus:</p><blockquote><em>Monitoring suhu dan kelembapan pada area pertanian menggunakan sensor virtual DHT22.</em></blockquote><p>Data dari sensor akan dibaca oleh ESP32, kemudian dikemas menjadi payload seperti data yang dikirim oleh node LoRa.</p><p>Setelah itu, data akan divisualisasikan melalui Serial Monitor sebagai alur:</p><pre>Sensor DHT22<br>↓<br>ESP32 sebagai End Device<br>↓<br>Simulasi LoRa Uplink<br>↓<br>Gateway LoRaWAN Simulasi<br>↓<br>Network Server Simulasi<br>↓<br>Application/Dashboard Simulasi</pre><p>Karena kita menggunakan Wokwi, maka komunikasi LoRaWAN tidak benar-benar menggunakan gateway fisik. Namun, alur prosesnya akan kita simulasikan agar teman-teman memahami cara kerja konsepnya.</p><h3>Persiapan Session</h3><p>Sebelum mulai praktik, siapkan beberapa hal berikut:</p><ol><li>Browser, disarankan Google Chrome atau Microsoft Edge.</li><li>Akun Wokwi.</li><li>Koneksi internet.</li><li>Pemahaman dasar ESP32.</li><li>Pemahaman dasar sensor DHT22.</li><li>Arduino IDE, jika ingin mencoba dengan alat asli.</li></ol><p>Untuk simulasi Wokwi, komponen yang digunakan adalah:</p><ul><li>ESP32,</li><li>DHT22,</li><li>LED indikator,</li><li>Serial Monitor.</li></ul><p>Untuk alat asli, komponen yang dapat digunakan adalah:</p><ul><li>2 ESP32,</li><li>2 modul LoRa SX1276/SX1278/RFM95,</li><li>sensor DHT22,</li><li>breadboard,</li><li>kabel jumper,</li><li>antena LoRa,</li><li>laptop.</li></ul><h3>Setup Sederhana Wokwi</h3><p>Langkah pertama, buka Wokwi lalu buat project baru menggunakan board <strong>ESP32</strong>.</p><p>Setelah itu, tambahkan sensor <strong>DHT22</strong> sebagai sensor suhu dan kelembapan.</p><p>Pada simulasi ini, DHT22 akan digunakan sebagai sumber data. Data yang dibaca akan dibuat seolah-olah dikirim melalui LoRa menuju gateway.</p><p>Hubungan pin yang digunakan:</p><p>KomponenPin KomponenPin ESP32DHT22VCC3V3DHT22GNDGNDDHT22DATAGPIO 15LED TXAnodaGPIO 2LED GatewayAnodaGPIO 4LED ServerAnodaGPIO 5</p><p>Fungsi LED:</p><ul><li>LED TX menyala saat data dikirim oleh end device.</li><li>LED Gateway menyala saat data diterima gateway simulasi.</li><li>LED Server menyala saat data masuk ke server simulasi.</li></ul><h3>Code Simulasi Wokwi</h3><p>Berikut adalah kode program yang digunakan.</p><p>Kode ini membaca data suhu dan kelembapan dari sensor DHT22, lalu menampilkan simulasi alur pengiriman data dari end device menuju gateway dan server.</p><pre>#include &quot;DHTesp.h&quot;</pre><pre>#define DHT_PIN 15<br>#define LED_TX 2<br>#define LED_GATEWAY 4<br>#define LED_SERVER 5</pre><pre>DHTesp dhtSensor;</pre><pre>int packetId = 1;</pre><pre>void setup() {<br>  Serial.begin(115200);</pre><pre>  dhtSensor.setup(DHT_PIN, DHTesp::DHT22);</pre><pre>  pinMode(LED_TX, OUTPUT);<br>  pinMode(LED_GATEWAY, OUTPUT);<br>  pinMode(LED_SERVER, OUTPUT);</pre><pre>  digitalWrite(LED_TX, LOW);<br>  digitalWrite(LED_GATEWAY, LOW);<br>  digitalWrite(LED_SERVER, LOW);</pre><pre>  Serial.println(&quot;======================================&quot;);<br>  Serial.println(&quot; Simulasi LoRa dan LoRaWAN - AMCC IoT &quot;);<br>  Serial.println(&quot;======================================&quot;);<br>  Serial.println(&quot;End Device siap membaca data sensor...&quot;);<br>  Serial.println();<br>}</pre><pre>void loop() {<br>  TempAndHumidity data = dhtSensor.getTempAndHumidity();</pre><pre>  if (isnan(data.temperature) || isnan(data.humidity)) {<br>    Serial.println(&quot;Gagal membaca sensor DHT22!&quot;);<br>    delay(3000);<br>    return;<br>  }</pre><pre>  String status;</pre><pre>  if (data.temperature &gt;= 35) {<br>    status = &quot;PANAS&quot;;<br>  } else if (data.temperature &lt;= 20) {<br>    status = &quot;DINGIN&quot;;<br>  } else {<br>    status = &quot;NORMAL&quot;;<br>  }</pre><pre>  String payload = &quot;{&quot;;<br>  payload += &quot;\&quot;packet_id\&quot;:&quot; + String(packetId) + &quot;,&quot;;<br>  payload += &quot;\&quot;temperature\&quot;:&quot; + String(data.temperature, 2) + &quot;,&quot;;<br>  payload += &quot;\&quot;humidity\&quot;:&quot; + String(data.humidity, 1) + &quot;,&quot;;<br>  payload += &quot;\&quot;status\&quot;:\&quot;&quot; + status + &quot;\&quot;&quot;;<br>  payload += &quot;}&quot;;</pre><pre>  Serial.println(&quot;--------------------------------------------------&quot;);<br>  Serial.println(&quot;END DEVICE&quot;);<br>  Serial.println(&quot;Membaca data dari sensor DHT22...&quot;);<br>  Serial.print(&quot;Suhu       : &quot;);<br>  Serial.print(data.temperature, 2);<br>  Serial.println(&quot; °C&quot;);</pre><pre>  Serial.print(&quot;Kelembapan : &quot;);<br>  Serial.print(data.humidity, 1);<br>  Serial.println(&quot; %&quot;);</pre><pre>  Serial.print(&quot;Status     : &quot;);<br>  Serial.println(status);</pre><pre>  Serial.println();<br>  Serial.println(&quot;Membuat payload data sensor...&quot;);<br>  Serial.println(payload);</pre><pre>  digitalWrite(LED_TX, HIGH);<br>  delay(700);<br>  digitalWrite(LED_TX, LOW);</pre><pre>  Serial.println();<br>  Serial.println(&quot;LoRa Uplink&quot;);<br>  Serial.println(&quot;Paket data dikirim melalui simulasi komunikasi radio jarak jauh...&quot;);<br>  delay(1000);</pre><pre>  digitalWrite(LED_GATEWAY, HIGH);<br>  delay(700);<br>  digitalWrite(LED_GATEWAY, LOW);</pre><pre>  Serial.println();<br>  Serial.println(&quot;LoRaWAN Gateway&quot;);<br>  Serial.println(&quot;Gateway menerima paket dari end device.&quot;);<br>  Serial.println(&quot;Gateway meneruskan data ke network server melalui koneksi internet.&quot;);<br>  delay(1000);</pre><pre>  digitalWrite(LED_SERVER, HIGH);<br>  delay(700);<br>  digitalWrite(LED_SERVER, LOW);</pre><pre>  Serial.println();<br>  Serial.println(&quot;Network Server / Application Server&quot;);<br>  Serial.println(&quot;Data berhasil diproses dan siap ditampilkan pada dashboard.&quot;);<br>  Serial.println(&quot;Data akhir:&quot;);<br>  Serial.println(payload);</pre><pre>  Serial.println(&quot;--------------------------------------------------&quot;);<br>  Serial.println();</pre><pre>  packetId++;</pre><pre>  delay(7000);<br>}</pre><h3>File Library Wokwi</h3><p>Agar kode berjalan, tambahkan file libraries.txt pada project Wokwi.</p><p>Isi file tersebut dengan:</p><pre>DHT sensor library for ESPx</pre><p>Library ini digunakan agar ESP32 dapat membaca sensor DHT22 dengan lebih mudah.</p><h3>Alur Kerja Program</h3><p>Program berjalan dengan alur berikut:</p><ol><li>ESP32 membaca data suhu dan kelembapan dari DHT22.</li><li>Data sensor dikemas menjadi format JSON.</li><li>ESP32 bertindak sebagai end device.</li><li>Program menampilkan simulasi proses pengiriman data melalui LoRa uplink.</li><li>Gateway simulasi menerima paket data.</li><li>Network server simulasi memproses data.</li><li>Data akhir ditampilkan pada Serial Monitor.</li></ol><p>Contoh output pada Serial Monitor:</p><pre>END DEVICE<br>Membaca data dari sensor DHT22...<br>Suhu       : 28.00 °C<br>Kelembapan : 70.0 %<br>Status     : NORMAL</pre><pre>Membuat payload data sensor...<br>{&quot;packet_id&quot;:1,&quot;temperature&quot;:28.00,&quot;humidity&quot;:70.0,&quot;status&quot;:&quot;NORMAL&quot;}</pre><pre>LoRa Uplink<br>Paket data dikirim melalui simulasi komunikasi radio jarak jauh...</pre><pre>LoRaWAN Gateway<br>Gateway menerima paket dari end device.<br>Gateway meneruskan data ke network server melalui koneksi internet.</pre><pre>Network Server / Application Server<br>Data berhasil diproses dan siap ditampilkan pada dashboard.</pre><h3>Penjelasan Singkat Kode</h3><p>Pada bagian awal program, kita memanggil library DHT:</p><pre>#include &quot;DHTesp.h&quot;</pre><p>Library ini digunakan untuk membaca sensor DHT22.</p><p>Kemudian kita mendefinisikan pin yang digunakan:</p><pre>#define DHT_PIN 15<br>#define LED_TX 2<br>#define LED_GATEWAY 4<br>#define LED_SERVER 5</pre><p>Pin DHT_PIN digunakan untuk sensor DHT22, sedangkan tiga LED digunakan sebagai indikator proses pengiriman data.</p><p>Pada bagian loop(), ESP32 akan membaca suhu dan kelembapan:</p><pre>TempAndHumidity data = dhtSensor.getTempAndHumidity();</pre><p>Setelah itu, data sensor dibuat menjadi payload JSON:</p><pre>String payload = &quot;{&quot;;<br>payload += &quot;\&quot;packet_id\&quot;:&quot; + String(packetId) + &quot;,&quot;;<br>payload += &quot;\&quot;temperature\&quot;:&quot; + String(data.temperature, 2) + &quot;,&quot;;<br>payload += &quot;\&quot;humidity\&quot;:&quot; + String(data.humidity, 1) + &quot;,&quot;;<br>payload += &quot;\&quot;status\&quot;:\&quot;&quot; + status + &quot;\&quot;&quot;;<br>payload += &quot;}&quot;;</pre><p>Payload ini menggambarkan data yang dikirim oleh perangkat IoT.</p><h3>Kenapa Menggunakan Format JSON?</h3><p>JSON digunakan karena formatnya ringan dan mudah dibaca oleh manusia maupun aplikasi.</p><p>Contohnya:</p><pre>{<br>  &quot;packet_id&quot;: 1,<br>  &quot;temperature&quot;: 28.00,<br>  &quot;humidity&quot;: 70.0,<br>  &quot;status&quot;: &quot;NORMAL&quot;<br>}</pre><p>Format seperti ini sangat umum digunakan pada sistem IoT karena mudah dikirim ke server, database, dashboard, maupun platform cloud.</p><h3>Mengenal Gateway pada LoRaWAN</h3><p>Pada sistem LoRaWAN, gateway memiliki peran yang sangat penting.</p><p>Gateway bertugas menerima data dari end device yang dikirim melalui LoRa, lalu meneruskannya ke network server melalui koneksi internet.</p><p>Dengan kata lain, gateway adalah jembatan antara perangkat sensor dan server.</p><p>Contoh alurnya:</p><pre>Sensor DHT22<br>↓<br>ESP32 + Modul LoRa<br>↓<br>LoRaWAN Gateway<br>↓<br>Network Server<br>↓<br>Dashboard Monitoring</pre><p>Tanpa gateway, data dari perangkat LoRa tidak bisa langsung masuk ke server LoRaWAN.</p><h3>Contoh Implementasi Alat Asli</h3><p>Jika ingin mencoba menggunakan alat asli, kita bisa menggunakan dua ESP32 dan dua modul LoRa.</p><p>Satu ESP32 bertugas sebagai <strong>sender</strong>, sedangkan ESP32 lainnya bertugas sebagai <strong>receiver</strong>.</p><p>Alurnya seperti ini:</p><pre>ESP32 Sender + DHT22 + LoRa<br>↓<br>Komunikasi Radio LoRa<br>↓<br>ESP32 Receiver + LoRa<br>↓<br>Serial Monitor / Dashboard</pre><p>Untuk koneksi modul LoRa SX1278 ke ESP32, contoh pin yang dapat digunakan:</p><p>Modul LoRaESP32VCC3.3VGNDGNDSCKGPIO 18MISOGPIO 19MOSIGPIO 23NSS/CSGPIO 5RSTGPIO 14DIO0GPIO 26</p><p>Catatan penting: modul LoRa biasanya menggunakan tegangan 3.3V, jadi jangan langsung diberi 5V agar modul tidak rusak.</p><h3>Perbedaan Praktik Wokwi dan Alat Asli</h3><p>Pada Wokwi, kita fokus memahami alur komunikasi dan proses data.</p><p>Sedangkan pada alat asli, kita benar-benar menggunakan modul LoRa untuk mengirim data melalui sinyal radio.</p><p>Perbedaannya seperti berikut:</p><p>BagianSimulasi WokwiAlat AsliSensorVirtualFisikLoRaDisimulasikan melalui alur programMenggunakan modul LoRaGatewaySimulasi konsepGateway fisikJarakTidak diuji nyataBisa diuji di lapanganTujuanBelajar konsepImplementasi nyata</p><p>Jadi, simulasi Wokwi cocok untuk memahami konsep sebelum mencoba perangkat asli.</p><h3>Contoh Penerapan LoRa di Dunia Nyata</h3><p>LoRa dapat digunakan dalam berbagai bidang, seperti:</p><ol><li><strong>Smart Farming</strong><br>Untuk monitoring suhu, kelembapan tanah, dan kondisi lingkungan pertanian.</li><li><strong>Monitoring Banjir</strong><br>Untuk membaca ketinggian air sungai dari lokasi yang jauh.</li><li><strong>Smart City</strong><br>Untuk monitoring lampu jalan, tempat sampah pintar, dan kualitas udara.</li><li><strong>Peternakan</strong><br>Untuk melacak lokasi hewan atau memantau kondisi kandang.</li><li><strong>Industri</strong><br>Untuk monitoring mesin, tangki, atau area produksi yang luas.</li></ol><h3>Evaluasi</h3><p>Setelah menyelesaikan praktik ini, coba jawab beberapa pertanyaan berikut:</p><ol><li>Apa yang dimaksud dengan LPWAN?</li><li>Apa perbedaan LoRa dan LoRaWAN?</li><li>Mengapa LoRa cocok digunakan untuk komunikasi IoT jarak jauh?</li><li>Apa fungsi gateway pada LoRaWAN?</li><li>Mengapa data yang dikirim menggunakan LoRa biasanya berukuran kecil?</li><li>Sebutkan contoh penerapan LoRa di kehidupan nyata.</li><li>Apa perbedaan simulasi Wokwi dan implementasi alat asli?</li></ol><h3>Kesimpulan</h3><p>Pada materi ini, kita sudah belajar mengenai konsep dasar komunikasi IoT jarak jauh menggunakan LoRa dan LoRaWAN.</p><p>LoRa digunakan sebagai teknologi komunikasi radio jarak jauh, sedangkan LoRaWAN digunakan sebagai protokol jaringan yang mengatur komunikasi dari end device, gateway, network server, hingga application server.</p><p>Melalui simulasi Wokwi, kita memahami bagaimana data dari sensor dapat dibaca, dikemas menjadi payload, lalu dikirim melalui alur komunikasi jarak jauh secara konseptual.</p><p>Walaupun simulasi ini belum menggunakan gateway LoRaWAN asli, konsep yang dipelajari sudah cukup untuk memahami dasar komunikasi IoT jarak jauh sebelum mencoba implementasi menggunakan alat fisik.</p><p>Yey, akhirnya teman-teman sudah berhasil memahami dasar pengiriman data IoT jarak jauh menggunakan LoRa dan LoRaWAN.</p><p>Sampai jumpa di materi IoT selanjutnya.</p><h3><strong>link wokwi</strong></h3><p><a href="https://wokwi.com/projects/467802284829211649">Lora - Wokwi ESP32, STM32, Arduino Simulator</a></p><p>ini adalah simulasi lora</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=12a0d624f1e8" width="1" height="1" alt=""><hr><p><a href="https://medium.com/amcc-amikom/from-sensor-to-long-range-pengiriman-data-iot-jarak-jauh-menggunakan-lora-dan-lorawan-dengan-12a0d624f1e8">From Sensor to Long Range: Pengiriman Data IoT Jarak Jauh Menggunakan LoRa dan LoRaWAN dengan…</a> was originally published in <a href="https://medium.com/amcc-amikom">Amikom Computer Club</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[flutter Project #7 Edutrack: Build APK]]></title>
            <link>https://medium.com/amcc-amikom/flutter-project-7-edutrack-build-apk-a60b69cc20bf?source=rss----fe834c257357---4</link>
            <guid isPermaLink="false">https://medium.com/p/a60b69cc20bf</guid>
            <category><![CDATA[mobile-development]]></category>
            <category><![CDATA[mobile-app-development]]></category>
            <category><![CDATA[mobile-programming]]></category>
            <dc:creator><![CDATA[Dzikrialbantani]]></dc:creator>
            <pubDate>Fri, 26 Jun 2026 11:58:33 GMT</pubDate>
            <atom:updated>2026-06-26T11:58:32.158Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*j8Yw12iutFI-Ip1Pn-BoHw.png" /></figure><p>Halo Sobat AMCC!</p><p>Gimana nih kabarnya? Akhirnya kita sudah sampai di penghujung pelatihan Mobile Programming. Setelah beberapa pertemuan sebelumnya kita belajar membuat tampilan, menghubungkan aplikasi dengan Firebase, melakukan authentication, sampai mengelola data realtime menggunakan Cloud Firestore, sekarang waktunya kita melakukan tahap finishing.</p><p>Pada pertemuan terakhir ini, kita tidak lagi terlalu banyak menambahkan fitur baru. Fokus kita adalah merapikan identitas aplikasi, mengganti launcher icon, lalu membuat file APK agar aplikasi bisa diinstal dan diuji langsung di perangkat Android.</p><p>Pada pelatihan kali ini, kita akan membahas:</p><p><a href="#c6f1">1. Persiapan Project</a><br><a href="#1f9d">2. Android Manifest Edit</a><br><a href="#7c8e">4. Package Implementation: Flutter Launcher Icons</a><br><a href="#afd0">5. Mengatur Versioning Aplikasi</a><br><a href="#9bed">6. Build APK</a><br><a href="#aa8c">8. Install APK ke Device</a><br><a href="#dd59">9. Troubleshooting</a></p><h3>1. Persiapan Project</h3><p>Sebelum masuk ke tahap finishing, pastikan project dari pertemuan sebelumnya sudah berjalan dengan baik.</p><p>Buka terminal pada folder project Flutter, lalu jalankan:</p><pre>flutter pub get<br>flutter run</pre><p>Pastikan aplikasi masih bisa dibuka, login berjalan, dan data dari Firestore masih tampil dengan benar.</p><p>Jika terdapat error setelah beberapa kali perubahan project, teman-teman bisa menjalankan:</p><pre>flutter clean<br>flutter pub get<br>flutter run</pre><p>Tahap ini penting karena sebelum aplikasi dibuild menjadi APK, kita harus memastikan project tidak memiliki error dasar.</p><h3>2. Android Manifest Edit</h3><p>Android Manifest adalah file konfigurasi utama pada aplikasi Android. File ini berisi informasi penting seperti nama aplikasi, permission, activity utama, icon aplikasi, dan beberapa konfigurasi lain yang dibutuhkan Android.</p><p>Pada project Flutter, file Android Manifest dapat ditemukan di:</p><pre>&lt;manifest xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;&gt;<br>    &lt;queries&gt;<br>        &lt;intent&gt;<br>            &lt;action android:name=&quot;android.intent.action.SENDTO&quot; /&gt;<br>            &lt;data android:scheme=&quot;mailto&quot; /&gt;<br>        &lt;/intent&gt;<br>        &lt;intent&gt;<br>            &lt;action android:name=&quot;android.intent.action.DIAL&quot; /&gt;<br>            &lt;data android:scheme=&quot;tel&quot; /&gt;<br>        &lt;/intent&gt;<br>    &lt;/queries&gt;<br>    &lt;application<br>        android:label=&quot;Edutrack&quot;<br>        android:name=&quot;${applicationName}&quot;<br>        android:icon=&quot;@mipmap/ic_launcher&quot;&gt;<br>        &lt;activity<br>            android:name=&quot;.MainActivity&quot;<br>            android:exported=&quot;true&quot;<br>            android:launchMode=&quot;singleTop&quot;<br>            android:taskAffinity=&quot;&quot;<br>            android:theme=&quot;@style/LaunchTheme&quot;<br>            android:configChanges=&quot;orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode&quot;<br>            android:hardwareAccelerated=&quot;true&quot;<br>            android:windowSoftInputMode=&quot;adjustResize&quot;&gt;<br>            &lt;!-- Specifies an Android theme to apply to this Activity as soon as<br>                 the Android process has started. This theme is visible to the user<br>                 while the Flutter UI initializes. After that, this theme continues<br>                 to determine the Window background behind the Flutter UI. --&gt;<br>            &lt;meta-data<br>              android:name=&quot;io.flutter.embedding.android.NormalTheme&quot;<br>              android:resource=&quot;@style/NormalTheme&quot;<br>              /&gt;<br>            &lt;intent-filter&gt;<br>                &lt;action android:name=&quot;android.intent.action.MAIN&quot;/&gt;<br>                &lt;category android:name=&quot;android.intent.category.LAUNCHER&quot;/&gt;<br>            &lt;/intent-filter&gt;<br>        &lt;/activity&gt;<br>        &lt;!-- Don&#39;t delete the meta-data below.<br>             This is used by the Flutter tool to generate GeneratedPluginRegistrant.java --&gt;<br>        &lt;meta-data<br>            android:name=&quot;flutterEmbedding&quot;<br>            android:value=&quot;2&quot; /&gt;<br>    &lt;/application&gt;<br>    &lt;!-- Required to query activities that can process text, see:<br>         https://developer.android.com/training/package-visibility and<br>         https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.<br><br>         In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. --&gt;<br>    &lt;queries&gt;<br>        &lt;intent&gt;<br>            &lt;action android:name=&quot;android.intent.action.PROCESS_TEXT&quot;/&gt;<br>            &lt;data android:mimeType=&quot;text/plain&quot;/&gt;<br>        &lt;/intent&gt;<br>    &lt;/queries&gt;<br>&lt;/manifest&gt;</pre><p>Ada beberapa bagian yang perlu kita pahami:</p><p><strong>android:label</strong></p><p>Bagian ini digunakan untuk menentukan nama aplikasi yang muncul di perangkat Android.</p><p>Contoh:</p><p><strong>android:label=”Edutrack”</strong></p><p>Jika nama aplikasi masih menggunakan nama project default, ubah menjadi nama aplikasi final.</p><p><strong>android:icon</strong></p><p>Bagian ini digunakan untuk menentukan icon aplikasi.</p><p><strong>android:icon=”@mipmap/ic_launcher”</strong></p><p>Nantinya icon ini akan diganti secara otomatis menggunakan package flutter_launcher_icons.</p><h3>4. Package Implementation: Flutter Launcher Icons</h3><p>Secara default, aplikasi Flutter menggunakan icon bawaan Flutter. Agar aplikasi terlihat lebih profesional dan sesuai dengan identitas aplikasi yang dibuat, kita akan mengganti launcher icon menggunakan package <a href="https://pub.dev/packages?q=flutter_launcher_icons">flutter_launcher_icons.</a></p><p>Package ini membantu kita membuat icon aplikasi Android secara otomatis ke berbagai ukuran yang dibutuhkan, sehingga kita tidak perlu membuat ukuran icon satu per satu secara manual.</p><p>Pada project ini, kita sudah menyiapkan file logo aplikasi dengan nama:</p><p><strong>assets/edutrack-logo.png</strong></p><p>File tersebut nantinya akan digunakan sebagai sumber utama untuk membuat launcher icon aplikasi.</p><p>Disarankan file logo menggunakan format PNG dengan ukuran yang cukup besar, misalnya 1024 x 1024 px, agar hasil icon tetap terlihat tajam ketika di generate ke berbagai ukuran Android.</p><h4>Menambahkan Package</h4><p>Untuk menambahkan package flutter_launcher_icons, buka terminal pada folder project Flutter, lalu jalankan command berikut:</p><pre>flutter pub add flutter_launcher_icons</pre><p>Command tersebut akan menambahkan package flutter_launcher_icons ke bagian dev_dependencies secara otomatis di file pubspec.yaml.</p><p>Setelah command berhasil dijalankan, kurang lebih bagian dev_dependencies akan menjadi seperti berikut:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/521/1*sDtFCBWZPo4Dgp44rA0cuA.png" /></figure><p>Versi package dapat menyesuaikan dengan versi terbaru yang terinstall pada project masing-masing.</p><p>Setelah package berhasil ditambahkan, buka file pubspec.yaml, lalu tambahkan konfigurasi berikut di bagian bawah file:</p><pre>flutter_launcher_icons:<br>  android: true<br>  ios: false<br>  image_path: &quot;assets/edutrack-logo.png&quot;<br>  min_sdk_android: 21</pre><p>Penjelasan singkat dari konfigurasi tersebut:</p><p><strong>android: true — </strong>Artinya launcher icon akan digenerate untuk platform Android.</p><p><strong>ios: false — </strong>Artinya launcher icon tidak digenerate untuk iOS karena pada pelatihan ini kita hanya fokus pada Android.</p><p><strong>image_path: “assets/edutrack-logo.png” — </strong>Bagian ini menunjukkan lokasi file logo yang akan digunakan sebagai icon aplikasi.</p><p><strong>min_sdk_android: 21 — </strong>Bagian ini menentukan minimum SDK Android yang digunakan untuk proses generate icon.</p><p>Pastikan penulisan indentasi pada file pubspec.yaml sudah benar. Pada file YAML, kesalahan spasi atau posisi baris bisa menyebabkan konfigurasi tidak terbaca.</p><h3>Generate Launcher Icon</h3><p>Setelah konfigurasi selesai, jalankan command berikut:</p><pre>flutter pub get</pre><p>Kemudian generate launcher icon dengan command:</p><pre>dart run flutter_launcher_icons</pre><p>Jika proses berhasil, package akan otomatis mengganti icon aplikasi Android berdasarkan file: <strong>assets/edutrack-logo.png</strong></p><p>Setelah itu, jalankan kembali aplikasi:</p><pre>flutter run</pre><p>Jika icon belum berubah di emulator atau HP, coba uninstall aplikasi lama terlebih dahulu, lalu jalankan kembali project Flutter. Hal ini biasanya terjadi karena perangkat masih menyimpan cache icon dari aplikasi sebelumnya.</p><h3>5. Mengatur Versioning Aplikasi</h3><p>Sebelum melakukan build APK, kita bisa mengatur versi aplikasi melalui file:</p><p>pubspec.yaml</p><p>Cari bagian berikut:</p><p><strong>version: 1.0.0+1</strong></p><p>Bagian versioning tersebut terdiri dari dua bagian:</p><p>1.0.0 — disebut <strong>version name</strong>, yaitu versi aplikasi yang biasanya terlihat oleh pengguna.</p><p>Sedangkan:</p><p>+1 — disebut <strong>version code</strong>, yaitu kode versi yang dibaca oleh sistem Android.</p><p>Contohnya, jika aplikasi pertama kali dibuat, kita bisa menggunakan:</p><p>version: 1.0.0+1</p><p>Jika aplikasi diperbarui, misalnya ada perbaikan bug atau penambahan fitur kecil, versinya bisa dinaikkan menjadi:</p><p>version: 1.0.1+2</p><p>Artinya, 1.0.1 adalah versi baru aplikasi, sedangkan +2 adalah kode versi terbaru yang dikenali oleh Android.</p><p>Hal yang perlu diperhatikan adalah <strong>version code harus selalu dinaikkan</strong> setiap kali aplikasi ingin dibuat dalam versi baru. Jangan menurunkan version code, karena Android dapat menganggap aplikasi tersebut sebagai versi yang lebih lama.</p><p>Secara sederhana:</p><ul><li>1.0.0 adalah versi aplikasi.</li><li>+1 adalah kode versi Android.</li></ul><p>Dengan mengatur versioning, aplikasi menjadi lebih rapi dan mudah dibedakan ketika nanti ada pembaruan versi.</p><h3>6. Build APK</h3><p>Setelah semua konfigurasi selesai, tahap terakhir adalah membuat file APK.</p><p>APK adalah file installer Android yang bisa dibagikan dan diinstal langsung pada perangkat Android.</p><p>Sebelum melakukan build, jalankan command berikut untuk membersihkan project dan mengambil ulang dependency:</p><pre>flutter clean<br>flutter pub get</pre><p>Setelah itu, buat file APK dengan command:</p><pre>flutter build apk --release</pre><p>Tunggu sampai proses build selesai. Jika berhasil, file APK akan tersimpan di folder berikut:</p><p><strong>build/app/outputs/flutter-apk/app-release.apk</strong></p><p>File app-release.apk tersebut adalah file yang bisa dibagikan ke pengguna atau tester untuk diinstal pada perangkat Android.</p><p>Jika ingin membuka folder hasil build dengan cepat, teman-teman bisa masuk ke folder project, lalu ikuti path:</p><pre>build &gt; app &gt; outputs &gt; flutter-apk &gt; app-release.apk</pre><h3>8. Install APK ke Device</h3><p>Untuk mencoba aplikasi pada HP Android, teman-teman bisa mengirim file APK ke perangkat lalu menginstallnya secara manual.</p><p>Jika HP terhubung ke laptop menggunakan USB Debugging, bisa juga menggunakan command:</p><pre>flutter install</pre><p>Pastikan perangkat sudah terdeteksi dengan command:</p><pre>flutter devices</pre><h3>9. Troubleshooting</h3><h3>Icon belum berubah</h3><p>Coba uninstall aplikasi lama dari emulator atau HP, lalu jalankan kembali:</p><pre>flutter clean<br>flutter pub get<br>dart run flutter_launcher_icons<br>flutter run</pre><h3>Firebase tidak bisa membaca data</h3><p>Pastikan:</p><ul><li>Koneksi internet aktif.</li><li>File google-services.json ada di folder android/app/.</li><li>Firebase sudah diinisialisasi di project Flutter.</li><li>Rules Firestore sudah dipublish.</li><li>User sudah login sebelum mengakses data.</li></ul><h3>Build APK gagal</h3><p>Coba jalankan:</p><pre>flutter clean<br>flutter pub get<br>flutter doctor</pre><p>Lalu cek pesan error yang muncul di terminal. Biasanya error build disebabkan oleh dependency, konfigurasi Gradle, atau file Android yang belum sesuai.</p><h3>APK tidak bisa diinstall</h3><p>Pastikan:</p><ul><li>Versi Android HP sesuai dengan minimum SDK aplikasi.</li><li>File APK tidak rusak.</li><li>Aplikasi lama dengan Application ID yang sama sudah dihapus terlebih dahulu.</li><li>Izinkan instalasi aplikasi dari sumber tidak dikenal jika install manual.</li></ul><p>Sampai di sini, aplikasi Flutter kita sudah melewati tahap finishing dan siap dibuat menjadi APK.</p><p>Pada pelatihan ini, kita sudah belajar bagaimana merapikan konfigurasi Android, mengganti launcher icon, memastikan Firebase tetap berjalan, mengatur versi aplikasi, hingga membuat file APK yang bisa diinstall pada perangkat Android.</p><p>Dengan begitu, project Flutter yang sebelumnya hanya berjalan di emulator atau mode development sekarang sudah bisa dibagikan dan diuji secara langsung.</p><p>Selamat! Teman-teman sudah berhasil menyelesaikan rangkaian pelatihan Mobile Programming dari awal sampai tahap build aplikasi.</p><p>Sampai jumpa di project dan pelatihan berikutnya.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a60b69cc20bf" width="1" height="1" alt=""><hr><p><a href="https://medium.com/amcc-amikom/flutter-project-7-edutrack-build-apk-a60b69cc20bf">flutter Project #7 Edutrack: Build APK</a> was originally published in <a href="https://medium.com/amcc-amikom">Amikom Computer Club</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Flutter #6: Mengelola Data Realtime Berbasis Cloud Firestore]]></title>
            <link>https://medium.com/amcc-amikom/flutter-6-mengelola-data-realtime-berbasis-cloud-firestore-3306fb5d0bff?source=rss----fe834c257357---4</link>
            <guid isPermaLink="false">https://medium.com/p/3306fb5d0bff</guid>
            <category><![CDATA[mobile-app-development]]></category>
            <category><![CDATA[firebase-cloud-functions]]></category>
            <dc:creator><![CDATA[Firdaus Chuzaeni]]></dc:creator>
            <pubDate>Sat, 20 Jun 2026 00:40:09 GMT</pubDate>
            <atom:updated>2026-06-20T00:40:07.894Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*kd-05vvVJl5S0QAAUA8MWQ.png" /></figure><p>Halo Sobat AMCC! 👋</p><p>Gimana nih kabarnya, semoga tetap sehat ya 😁</p><p>Kita balik lagi ke pelatihan kita nih. Pada pelatihan sebelumnya kita sudah belajar membuat Halaman Profil yang bagus nih. Setelah ini, kita akan lanjut ke materi yang tentunya lebih seru lagi dong 👍</p><p>Pada pelatihan kali ini, kita akan belajar bagaimana cara mengimplementasikan operasi dasar data, yakni <strong>CRUD</strong> (Create, Read, Update, Delete). Kerennya lagi, data yang kita kelola nanti bakal tersinkronisasi secara <em>realtime</em> lho! Jadi begitu ada data yang ditambah atau diubah di server Firebase, layar aplikasi Flutter kita bakal otomatis terupdate secara instan tanpa perlu di-refresh manual.</p><p><strong>· </strong><a href="#c240"><strong>Apa Itu CRUD?</strong></a><strong><br>· </strong><a href="#5a35"><strong>Persiapan Project</strong></a><strong><br>· </strong><a href="#b288"><strong>Membuka Firebase Console</strong></a><strong><br>· </strong><a href="#338b"><strong>Rules Firestore</strong></a><strong><br>· </strong><a href="#0be6"><strong>Membuat Collection Baru</strong></a><strong><br>· </strong><a href="#d011"><strong>Membuat Task Model</strong></a><strong><br>· </strong><a href="#9346"><strong>Membuat Task Service</strong></a><strong><br>· </strong><a href="#60c3"><strong>Membuat Task Service</strong></a><strong><br>· </strong><a href="#c757"><strong>Full Kode</strong></a></p><p>Sebelum kita masuk terlalu dalam ke baris kode program, teman-teman wajib tahu dulu nih apa itu konsep <strong>CRUD</strong>.</p><h3><strong>Apa Itu CRUD?</strong></h3><p>CRUD sendiri merupakan singkatan dari Create, Read, Update, dan Delete, yang merupakan empat fungsi dasar yang wajib dimiliki oleh sebuah aplikasi interaktif ketika berurusan dengan manajemen database.</p><p>Sederhananya, Create bertugas memasukkan data baru, Read bertugas membaca dan menampilkan data ke layar HP, Update dipakai saat user ingin mengubah data yang sudah ada, dan Delete mengeksekusi penghapusan data secara permanen.</p><p>Nah, semua keajaiban manipulasi data ini akan kita titipkan pada Cloud Firestore, sebuah database NoSQL berbasis cloud yang fleksibel, cepat, mendukung query kompleks, dan punya kemampuan sinkronisasi data secara realtime. Jadi, begitu ada perubahan data di server, layar Flutter kita bakal ikut berubah otomatis tanpa perlu ditarik atau di-refresh manual, keren banget kan? 🚀🔥</p><h3><strong>Persiapan Project</strong></h3><p>Bagi teman-teman yang berhalangan hadir atau proyek pelatihan kemarin belum selesai, bisa menyalin proyek pertemuan sebelumnya.</p><pre>git clone -b pertemuan-9 https://github.com/amccamikom/amcc-mobile-2025.git pertemuan-10</pre><h3><strong>Membuka Firebase Console</strong></h3><p>Langkah selanjutnya adalah kita buka kembali browser kesayangan kita dan akses halaman Firebase Console. Di sana, teman-teman cukup cari dan klik project kemarin yang sudah dibuat yaitu project bernama edutrack yang sudah kita integrasikan dengan aplikasi Flutter kita pada modul sebelumnya.</p><p>Setelah masuk ke dalam dashboard project tersebut, langsung saja meluncur ke menu Databases &amp; Storage di bilah navigasi kiri, lalu pilih Firestore Database. Karena pada pertemuan sebelumnya kita sudah mengaktifkan database ini untuk menyimpan data users, kali ini kita tidak perlu membuat database baru lagi dari nol!</p><h3><strong>Rules Firestore</strong></h3><ul><li>Masuk ke <strong>Firestore</strong> &gt; <strong>Rules</strong></li><li>Tambahkan Kode dibawah ini</li></ul><pre>match /tasks/{taskId} {<br>   allow create: if request.auth != null;<br>   allow read, update, delete: if request.auth != null &amp;&amp; resource.data.userId == request.auth.uid;<br>}</pre><ul><li>Kemudian Klik <strong>Publish</strong></li></ul><h3><strong>Membuat Collection Baru</strong></h3><p>Di dalam halaman Firestore Database yang sudah aktif tersebut, langkah yang harus kita lakukan sekarang adalah membuat Collection baru sebagai tempat penyimpanan tugas. Teman-teman bisa langsung klik tombol + Start collection yang ada di tab Data.</p><ul><li>Berikan nama Collectionnya <strong>Tasks</strong>.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*1Y5wk34aZCRzZ27QgP4E3g.png" /></figure><ul><li>Untuk mengisi ID secara manual, kita perlu mendapatkan User UID dari user yang sudah dibuat.</li><li>Kemudian isi struktur tabel sesuai data di bawah ini :</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*IfuBQAeYxKZJ-E3oUsvitQ.png" /></figure><h3><strong>Membuat Task Model</strong></h3><p>Task Model ini berfungsi sebagai cetak biru (blueprint) data objek tugas kita. Di dalamnya terdapat fungsi jembatan untuk mengubah data JSON Firebase menjadi objek Dart, dan sebaliknya.</p><ul><li>Buat Folder baru bernama <strong>models.</strong></li><li>Buat File baru bernama <strong>task_model.dart.</strong></li></ul><pre>import &#39;package:cloud_firestore/cloud_firestore.dart&#39;;<br><br>class TaskModel {<br>  String id;<br>  String userId; <br>  String title;<br>  String date;<br>  bool isCompleted;<br>  Timestamp? createdAt;<br><br>  TaskModel({<br>    required this.id,<br>    required this.userId,<br>    required this.title,<br>    required this.date,<br>    required this.isCompleted,<br>    this.createdAt,<br>  });<br><br>  factory TaskModel.fromFirestore(Map&lt;String, dynamic&gt; json, String documentId) {<br>    return TaskModel(<br>      id: documentId,<br>      userId: json[&#39;userId&#39;] ?? &#39;&#39;, <br>      title: json[&#39;title&#39;] ?? &#39;&#39;,<br>      date: json[&#39;date&#39;] ?? &#39;&#39;,<br>      isCompleted: json[&#39;isCompleted&#39;] ?? false,<br>      createdAt: json[&#39;createdAt&#39;] as Timestamp?,<br>    );<br>  }<br><br>  Map&lt;String, dynamic&gt; toFirestore() {<br>    return {<br>      &#39;userId&#39;: userId, <br>      &#39;title&#39;: title,<br>      &#39;date&#39;: date,<br>      &#39;isCompleted&#39;: isCompleted,<br>      &#39;createdAt&#39;: createdAt ?? FieldValue.serverTimestamp(),<br>    };<br>  }<br>}</pre><h3><strong>Membuat Task Service</strong></h3><ul><li>Buka folder <strong>Services.</strong></li><li>Lalu buat File baru bernama <strong>task_service.dart.</strong></li></ul><pre>import &#39;package:cloud_firestore/cloud_firestore.dart&#39;;<br>import &#39;package:firebase_auth/firebase_auth.dart&#39;;<br>import &#39;../models/task_model.dart&#39;;<br><br>class TaskService {<br>  final CollectionReference _taskCollection =<br>      FirebaseFirestore.instance.collection(&#39;tasks&#39;);<br><br>  Future&lt;void&gt; addTask(String title, String date) async {<br>    try {<br>      final String currentUserId = FirebaseAuth.instance.currentUser!.uid;<br><br>      await _taskCollection.add({<br>        &#39;userId&#39;: currentUserId,<br>        &#39;title&#39;: title,<br>        &#39;date&#39;: date,<br>        &#39;isCompleted&#39;: false,<br>        &#39;createdAt&#39;: FieldValue.serverTimestamp(),<br>      });<br>    } catch (e) {<br>      print(&quot;Gagal menambahkan task: $e&quot;);<br>    }<br>  }<br><br>  Stream&lt;List&lt;TaskModel&gt;&gt; getTasksStream() {<br>    final String currentUserId = FirebaseAuth.instance.currentUser!.uid;<br><br>    return _taskCollection<br>        .where(&#39;userId&#39;, isEqualTo: currentUserId)<br>        .snapshots()<br>        .map((snapshot) {<br>          final tasks = snapshot.docs.map((doc) {<br>            return TaskModel.fromFirestore(doc.data() as Map&lt;String, dynamic&gt;, doc.id);<br>          }).toList();<br><br>          tasks.sort((a, b) {<br>            if (a.createdAt == null &amp;&amp; b.createdAt == null) return 0;<br>            if (a.createdAt == null) return 1;<br>            if (b.createdAt == null) return -1;<br>            return b.createdAt!.compareTo(a.createdAt!);<br>          });<br><br>          return tasks;<br>        });<br>  }<br><br>  Future&lt;void&gt; toggleTaskStatus(String id, bool currentStatus) async {<br>    try {<br>      await _taskCollection.doc(id).update({<br>        &#39;isCompleted&#39;: !currentStatus,<br>      });<br>    } catch (e) {<br>      print(&quot;Gagal memperbarui status task: $e&quot;);<br>    }<br>  }<br><br>  Future&lt;void&gt; deleteTask(String id) async {<br>    try {<br>      await _taskCollection.doc(id).delete();<br>    } catch (e) {<br>      print(&quot;Gagal menghapus task: $e&quot;);<br>    }<br>  }<br>}</pre><h3>Membuat Task Service</h3><p>Kita akan memanggil fungsi baca data menggunakan bantuan widget sakti bernama <strong>StreamBuilder</strong> yang dipasangkan langsung dengan stream dari Firestore Service kita.</p><ul><li>Buka file <strong>home_page.dart</strong>.</li><li>Cari Todo Section yang menggunakan <strong>Listview Builder</strong>.</li></ul><pre>StreamBuilder&lt;List&lt;TaskModel&gt;&gt;(<br>                  stream: _taskService.getTasksStream(),<br>                  builder: (context, snapshot) {<br>                    if (snapshot.hasError) {<br>                      return Center(<br>                        child: Padding(<br>                          padding: const EdgeInsets.all(20.0),<br>                          child: Text(<br>                            &#39;Gagal mengambil data tugas: ${snapshot.error}&#39;,<br>                            style: GoogleFonts.poppins(color: Colors.red),<br>                            textAlign: TextAlign.center,<br>                          ),<br>                        ),<br>                      );<br>                    }<br>  <br>                    // Tampilkan loading spinner jika data masih dalam proses loading<br>                    if (snapshot.connectionState == ConnectionState.waiting) {<br>                      return const Center(<br>                        child: Padding(<br>                          padding: EdgeInsets.all(20.0),<br>                          child: CircularProgressIndicator(),<br>                        ),<br>                      );<br>                    }<br>  <br>                    // Tangkap data list dari snapshot<br>                    final tasks = snapshot.data ?? [];<br>  <br>                    // Tampilkan teks jika collection database masih kosong<br>                    if (tasks.isEmpty) {<br>                      return Center(<br>                        child: Padding(<br>                          padding: const EdgeInsets.all(20.0),<br>                          child: Text(<br>                            &#39;Belum ada tugas. Yuk tambah tugas baru!&#39;,<br>                            style: GoogleFonts.poppins(color: const Color(0xFF64748B)),<br>                          ),<br>                        ),<br>                      );<br>                    }<br>  <br>                    // Render komponen TodoCard secara dinamis berdasarkan data Firebase<br>                    return ListView.builder(<br>                      shrinkWrap: true,<br>                      physics: const NeverScrollableScrollPhysics(),<br>                      itemCount: tasks.length,<br>                      itemBuilder: (context, index) {<br>                        final task = tasks[index];<br>                        return TodoCard(<br>                          title: task.title,<br>                          date: task.date,<br>                          isDone: task.isCompleted,<br>                          onCheckChanged: (bool? value) async {<br>                            // [UPDATE] Mengubah status check/uncheck tugas<br>                            await _taskService.toggleTaskStatus(task.id, task.isCompleted);<br>                          },<br>                          onDelete: () async {<br>                            // [DELETE] Menghapus data dari Firestore database<br>                            await _taskService.deleteTask(task.id);<br>                          },<br>                        );<br>                      },<br>                    );<br>                  },<br>                ),</pre><p><strong>StreamBuilder</strong> ini bertindak reaktif seperti jangkar; ia akan memantau database di awan secara terus-menerus dan otomatis memerintahkan aplikasi untuk menggambar ulang (re-render) widget <strong>ListView.builder</strong> di layar setiap kali ada aktivitas manipulasi data di server Firebase. Untuk memanjakan pengguna, kita juga menambahkan tombol FloatingActionButton berikon tanda tambah yang jika diklik akan memunculkan sebuah kotak dialog (showDialog) berisi form input teks untuk memasukkan judul dan deskripsi tugas baru, sekaligus merangkap sebagai form modifikasi saat ikon edit ditekan.</p><h3>Full Kode</h3><ul><li>home_page.dart</li></ul><pre>import &#39;package:flutter/material.dart&#39;;<br>import &#39;package:google_fonts/google_fonts.dart&#39;;<br>import &#39;../widgets/TodoCard.dart&#39;;<br>import &#39;../widgets/CarouselWidget.dart&#39;;<br>import &#39;../models/task_model.dart&#39;;<br>import &#39;../services/tasks_service.dart&#39;;<br>import &#39;../services/auth_service.dart&#39;;<br><br>class HomePage extends StatefulWidget {<br>  const HomePage({super.key});<br><br>  @override<br>  State&lt;HomePage&gt; createState() =&gt; _HomePageState();<br>}<br><br>class _HomePageState extends State&lt;HomePage&gt; {<br>  // Inisialisasi TaskService untuk memanggil fungsi CRUD<br>  final TaskService _taskService = TaskService();<br>  final AuthService _authService = AuthService();<br>  String _userName = &#39;user&#39;;<br><br>  @override<br>  void initState() {<br>    super.initState();<br>    _loadUserData();<br>  }<br><br>  Future&lt;void&gt; _loadUserData() async {<br>    try {<br>      final userData = await _authService.getUserData();<br>      if (userData != null &amp;&amp; userData[&#39;name&#39;] != null) {<br>        setState(() {<br>          _userName = userData[&#39;name&#39;];<br>        });<br>      }<br>    } catch (e) {<br>      print(&quot;Gagal mengambil data user: $e&quot;);<br>    }<br>  }<br><br>  void _showAddTaskDialog() {<br>    final titleController = TextEditingController();<br>    final dateController = TextEditingController();<br><br>    showDialog(<br>      context: context,<br>      builder: (dialogContext) {<br>        return AlertDialog(<br>          title: Text(<br>            &#39;Tambah task&#39;,<br>            style: GoogleFonts.poppins(<br>              fontSize: 18,<br>              fontWeight: FontWeight.w600,<br>            ),<br>          ),<br>          content: Column(<br>            mainAxisSize: MainAxisSize.min,<br>            children: [<br>              TextField(<br>                controller: titleController,<br>                decoration: const InputDecoration(<br>                  labelText: &#39;Judul task&#39;,<br>                  hintText: &#39;Contoh: Belajar Flutter&#39;,<br>                ),<br>              ),<br>              const SizedBox(height: 12),<br>              TextField(<br>                controller: dateController,<br>                decoration: const InputDecoration(<br>                  labelText: &#39;Tanggal&#39;,<br>                  hintText: &#39;Contoh: 6 Maret 2026&#39;,<br>                ),<br>              ),<br>            ],<br>          ),<br>          actions: [<br>            TextButton(<br>              onPressed: () {<br>                Navigator.pop(dialogContext);<br>              },<br>              child: const Text(&#39;Batal&#39;),<br>            ),<br>            ElevatedButton(<br>              onPressed: () async {<br>                final title = titleController.text.trim();<br>                final date = dateController.text.trim();<br><br>                if (title.isEmpty || date.isEmpty) {<br>                  return;<br>                }<br><br>                // [CREATE] Panggil fungsi tambah data ke Firebase<br>                await _taskService.addTask(title, date);<br><br>                if (mounted) {<br>                  Navigator.pop(dialogContext);<br>                }<br>              },<br>              child: const Text(&#39;Simpan&#39;),<br>            ),<br>          ],<br>        );<br>      },<br>    );<br>  }<br><br>  @override<br>  Widget build(BuildContext context) {<br>    return Scaffold(<br>      backgroundColor: const Color(0xFFF6F7FB),<br>      appBar: AppBar(<br>        backgroundColor: const Color(0xFF3254FD),<br>        elevation: 0,<br>        automaticallyImplyLeading: false,<br>        title: Row(<br>          children: [<br>            Image.asset(&#39;assets/edutrack-logo.png&#39;, width: 20, height: 20),<br>            const SizedBox(width: 8),<br>            Text(<br>              &#39;Edutrack&#39;,<br>              style: GoogleFonts.poppins(<br>                fontSize: 20,<br>                fontWeight: FontWeight.w500,<br>                color: Colors.white,<br>              ),<br>            ),<br>          ],<br>        ),<br>        actions: [<br>          IconButton(<br>            onPressed: () {<br>              _showAddTaskDialog();<br>            },<br>            icon: const Icon(Icons.add, color: Colors.white, size: 24),<br>          ),<br>        ],<br>      ),<br>      body: RefreshIndicator(<br>        onRefresh: () async {<br>          await _loadUserData();<br>        },<br>        child: SingleChildScrollView(<br>          physics: const AlwaysScrollableScrollPhysics(),<br>          child: Padding(<br>            padding: const EdgeInsets.fromLTRB(16, 20, 16, 20),<br>            child: Column(<br>              crossAxisAlignment: CrossAxisAlignment.start,<br>              children: [<br>                // ============ GREETING SECTION ============<br>                Text(<br>                  &#39;Halo, $_userName&#39;,<br>                  style: GoogleFonts.poppins(<br>                    fontSize: 22,<br>                    fontWeight: FontWeight.w700,<br>                    color: const Color(0xFF0F172A),<br>                  ),<br>                ),<br>                const SizedBox(height: 8),<br>                Text(<br>                  &#39;Selamat datang di aplikasi edutrack&#39;,<br>                  style: GoogleFonts.poppins(<br>                    fontSize: 13,<br>                    color: const Color(0xFF64748B),<br>                  ),<br>                ),<br>                const SizedBox(height: 20),<br>  <br>                // ============ CAROUSEL SECTION ============<br>                const CarouselWidget(),<br>                const SizedBox(height: 24),<br>  <br>                // ============ DAFTAR TUGAS SECTION ============<br>                Text(<br>                  &#39;Daftar Tugas&#39;,<br>                  style: GoogleFonts.poppins(<br>                    fontSize: 16,<br>                    fontWeight: FontWeight.w700,<br>                    color: const Color(0xFF0F172A),<br>                  ),<br>                ),<br>                const SizedBox(height: 12),<br>  <br>                // [READ] Menggunakan StreamBuilder untuk memantau data realtime<br>                StreamBuilder&lt;List&lt;TaskModel&gt;&gt;(<br>                  stream: _taskService.getTasksStream(),<br>                  builder: (context, snapshot) {<br>                    if (snapshot.hasError) {<br>                      return Center(<br>                        child: Padding(<br>                          padding: const EdgeInsets.all(20.0),<br>                          child: Text(<br>                            &#39;Gagal mengambil data tugas: ${snapshot.error}&#39;,<br>                            style: GoogleFonts.poppins(color: Colors.red),<br>                            textAlign: TextAlign.center,<br>                          ),<br>                        ),<br>                      );<br>                    }<br>  <br>                    // Tampilkan loading spinner jika data masih dalam proses loading<br>                    if (snapshot.connectionState == ConnectionState.waiting) {<br>                      return const Center(<br>                        child: Padding(<br>                          padding: EdgeInsets.all(20.0),<br>                          child: CircularProgressIndicator(),<br>                        ),<br>                      );<br>                    }<br>  <br>                    // Tangkap data list dari snapshot<br>                    final tasks = snapshot.data ?? [];<br>  <br>                    // Tampilkan teks jika collection database masih kosong<br>                    if (tasks.isEmpty) {<br>                      return Center(<br>                        child: Padding(<br>                          padding: const EdgeInsets.all(20.0),<br>                          child: Text(<br>                            &#39;Belum ada tugas. Yuk tambah tugas baru!&#39;,<br>                            style: GoogleFonts.poppins(color: const Color(0xFF64748B)),<br>                          ),<br>                        ),<br>                      );<br>                    }<br>  <br>                    // Render komponen TodoCard secara dinamis berdasarkan data Firebase<br>                    return ListView.builder(<br>                      shrinkWrap: true,<br>                      physics: const NeverScrollableScrollPhysics(),<br>                      itemCount: tasks.length,<br>                      itemBuilder: (context, index) {<br>                        final task = tasks[index];<br>                        return TodoCard(<br>                          title: task.title,<br>                          date: task.date,<br>                          isDone: task.isCompleted,<br>                          onCheckChanged: (bool? value) async {<br>                            // [UPDATE] Mengubah status check/uncheck tugas<br>                            await _taskService.toggleTaskStatus(task.id, task.isCompleted);<br>                          },<br>                          onDelete: () async {<br>                            // [DELETE] Menghapus data dari Firestore database<br>                            await _taskService.deleteTask(task.id);<br>                          },<br>                        );<br>                      },<br>                    );<br>                  },<br>                ),<br>              ],<br>            ),<br>          ),<br>        ),<br>      ),<br>    );<br>  }<br>}</pre><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=3306fb5d0bff" width="1" height="1" alt=""><hr><p><a href="https://medium.com/amcc-amikom/flutter-6-mengelola-data-realtime-berbasis-cloud-firestore-3306fb5d0bff">Flutter #6: Mengelola Data Realtime Berbasis Cloud Firestore</a> was originally published in <a href="https://medium.com/amcc-amikom">Amikom Computer Club</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Ngulik RESTful API dari Nol Sampai CRUD Jalan]]></title>
            <link>https://medium.com/amcc-amikom/ngulik-restful-api-dari-nol-sampai-crud-jalan-d37977c16d28?source=rss----fe834c257357---4</link>
            <guid isPermaLink="false">https://medium.com/p/d37977c16d28</guid>
            <category><![CDATA[backend]]></category>
            <category><![CDATA[web-development]]></category>
            <dc:creator><![CDATA[Brawidya]]></dc:creator>
            <pubDate>Fri, 12 Jun 2026 10:47:43 GMT</pubDate>
            <atom:updated>2026-06-12T10:47:42.611Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*0Fvnm0wX8yBSQeYED1uHag.png" /></figure><p>Gimana Kabar kalian, btw ini udah jadi pelatihan terakhir kalian lo ga kerasa kan, kita udah bertemu di 9 minggu kalo dipikir pikir udah lebih dari 2 bulan, dan kalian pasti sudah ada jago dan tau pondasi menjadi backend developer.</p><p>Kalau temen temen ketinggalan di pertemuan kemarin bisa dibaca baca terlebih dahulu, kemudian baru lanjut di sesi kali ini, tapi tenang aja ak bakal bahas tuntas sampai bersih di modul kali ini</p><p>Di modul kali ini kita bakal bantai bantai yang namanya RESTFULL API, kita bakal ngebuat api ni di laravel tapi apasih bedanya dengan api yang lain cekidot kita bahas…</p><h3>Table of Contents</h3><p>· <a href="#b08c">Apa itu API?</a><br>· <a href="#dcb7">Alasan kenapa memakai API</a><br>· <a href="#c10d">Cara kerja API</a> <br>· <a href="#ae0a">RESTful API</a><br>· <a href="#3dc6">Cara Kerja RESTful API</a><br>· <a href="#1ebd">Kerangka Kerja RESTful API</a><br>· <a href="#007a">1. URL (Endpoint)</a><br>· <a href="#3b1d">2. HTTP Method</a><br>· <a href="#8a67">3. Request Body</a><br>· <a href="#e72d">4. Headers</a><br>· <a href="#1694">5. Response</a><br>· <a href="#792a">Method RESTful API</a><br>· <a href="#53e8">GET</a><br>· <a href="#fb9a">POST</a><br>· <a href="#f062">PUT / PATCH</a><br>· <a href="#6060">PUT</a><br>· <a href="#0c1d">PATCH</a><br>· <a href="#415d">DELETE</a><br>· <a href="#9fe9">Setup-setup</a><br>· <a href="#352c">Implementasi RESTful API di Laravel</a><br>· <a href="#e20b">Persiapan</a><br>· <a href="#1e76">Setup API di Laravel Menggunakan Eloquent API Resources</a><br>· <a href="#d689">Apa itu Eloquent API Resources?</a><br>· <a href="#7651">Mendapatkan Semua Data Task dengan Method GET</a><br>· <a href="#50b0">Membuat Task Baru dengan Method POST</a><br>· <a href="#3d4c">Mengupdate Task Berdasarkan ID dengan Method PATCH</a><br>· <a href="#4f38">Menghapus Task dengan Method DELETE</a><br>· <a href="#8a12">Kesimpulan</a></p><h3>Apa itu API?</h3><p>Nah, sekarang kita masuk ke pembahasan utama, yaitu <strong>API</strong>. Kemungkinan besar istilah ini sudah nggak asing lagi buat kalian, apalagi setelah beberapa minggu belajar backend. Hampir semua aplikasi yang kita gunakan sehari-hari, mulai dari media sosial, e-commerce, sampai aplikasi perbankan, pasti memanfaatkan API di belakang layarnya.</p><p>Tapi sebenarnya, apa sih API itu?</p><p>API atau <strong>Application Programming Interface</strong> adalah sebuah perantara yang memungkinkan dua aplikasi atau lebih untuk saling berkomunikasi dan bertukar data. Bisa dibilang API ini seperti pelayan di restoran. Ketika pelanggan memesan makanan, pelayan akan menyampaikan pesanan tersebut ke dapur, lalu mengantarkan hasilnya kembali ke pelanggan. Nah, API bekerja dengan cara yang kurang lebih sama, yaitu menerima permintaan dari suatu aplikasi, meneruskannya ke sistem yang sesuai, lalu mengembalikan hasilnya.</p><p>“Berkomunikasi? Maksudnya aplikasi bisa ngobrol?”</p><p>Kurang lebih seperti itu . Bedanya, yang digunakan bukan bahasa manusia, melainkan format dan aturan yang sudah ditentukan dalam dunia pemrograman. Dengan adanya API, sebuah aplikasi tidak perlu mengetahui bagaimana sistem lain bekerja secara detail. Cukup kirim permintaan sesuai aturan yang berlaku, lalu terima data yang diberikan sebagai respons.</p><p>Karena itulah API menjadi salah satu komponen paling penting dalam pengembangan aplikasi modern, terutama bagi seorang Backend Developer. Sebelum mulai membuat RESTful API menggunakan Laravel, penting bagi kita untuk memahami terlebih dahulu konsep dasar yang ada di baliknya.</p><h3>Alasan kenapa memakai API</h3><p>Supaya lebih mudah dipahami, coba kita bayangkan sebuah kasus sederhana.</p><p>Misalnya kalian membuat sebuah <strong>To Do App</strong> yang digunakan untuk mencatat dan mengelola daftar tugas. Di dalam aplikasi tersebut terdapat berbagai data, seperti nama tugas, deskripsi, status pengerjaan, hingga tanggal deadline. Aplikasi yang kalian buat berjalan dengan baik dan mulai digunakan oleh banyak pengguna.</p><p>Nah, suatu hari ada developer lain yang ingin mengintegrasikan aplikasi mereka dengan To Do App milik kalian. Mereka ingin menampilkan daftar tugas pengguna atau membuat tugas baru langsung dari aplikasi mereka.</p><p>Kira-kira bagaimana cara kalian memberikan akses tersebut?</p><p>Apakah mereka langsung diberi akses ke database? Tentu tidak. Apakah mereka diberikan akun admin aplikasi? Jelas bukan solusi yang tepat. 🤔</p><p>Memberikan akses langsung ke database sangat berisiko karena pihak lain bisa saja melihat, mengubah, bahkan menghapus data yang seharusnya tidak boleh mereka akses. Selain itu, terdapat data-data yang bersifat privat dan harus tetap dijaga keamanannya.</p><p>Lalu bagaimana solusinya?</p><p>Di sinilah <strong>API (Application Programming Interface)</strong> berperan.</p><p>Dengan API, kita dapat menyediakan jalur komunikasi yang aman bagi aplikasi lain untuk berinteraksi dengan sistem yang kita miliki. Misalnya, kita dapat menyediakan endpoint untuk melihat daftar tugas, menambahkan tugas baru, mengubah status tugas, atau menghapus tugas tertentu. Semua itu dilakukan melalui aturan yang sudah kita tentukan sebelumnya.</p><p>Jadi, aplikasi lain tidak perlu mengetahui bagaimana struktur database kita atau bagaimana sistem bekerja di belakang layar. Mereka cukup mengirimkan request ke API yang tersedia, lalu API akan memproses permintaan tersebut dan mengembalikan response yang sesuai.</p><p>Singkatnya, API menjadi jembatan yang memungkinkan berbagai aplikasi saling berkomunikasi dengan aman tanpa harus membuka akses ke seluruh sistem yang ada. 🚀</p><h3>Cara kerja API</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*46gCOKVZrMeCSSfbPDGXdw.jpeg" /></figure><p>Cara kerja API sebenarnya cukup sederhana kok sobat. Supaya lebih mudah dipahami, kita lanjut menggunakan contoh <strong>To Do App</strong> yang sedang kita buat.</p><p>Misalnya ada seorang pengguna bernama Faris yang sedang membuka To Do App melalui browser. Ketika Faris ingin melihat daftar tugas yang sudah ia buat, apa yang sebenarnya terjadi di balik layar?</p><p>Yuk kita bedah alurnya satu per satu:</p><ol><li>Pertama, browser Faris (<strong>client</strong>) mengirimkan <strong>request</strong> ke server untuk meminta data daftar tugas yang dimilikinya.</li><li>Request tersebut dikirim melalui jaringan internet menuju server aplikasi.</li><li>Setelah sampai di server, request tidak langsung mengakses database. Request akan melewati <strong>API</strong> terlebih dahulu.</li><li>API menerima request tersebut lalu memeriksa apakah request yang dikirim valid dan sesuai dengan aturan yang telah ditentukan.</li><li>Jika valid, API akan meminta data yang dibutuhkan dari database.</li><li>Database kemudian mengembalikan data daftar tugas kepada API.</li><li>API mengolah data tersebut dan mengirimkannya kembali ke client dalam bentuk <strong>response</strong>, biasanya berupa data JSON.</li><li>Browser menerima response tersebut, lalu menampilkannya dalam bentuk daftar tugas yang bisa dilihat oleh Denis.</li></ol><p>Jadi secara sederhana, API bertugas sebagai perantara yang menerima permintaan dari client, mengambil atau mengelola data yang diperlukan, lalu mengirimkan hasilnya kembali ke client.</p><p>Simpel kan? 😉</p><h3>RESTful API</h3><h4>Apa itu RESTful API?</h4><p>RESTful API adalah jenis API yang paling sering digunakan dalam pengembangan aplikasi modern. Hampir semua aplikasi yang kita gunakan saat ini, mulai dari media sosial, marketplace, hingga aplikasi manajemen tugas, menggunakan RESTful API untuk bertukar data.</p><p>Tapi sebenarnya RESTful API itu apa sih?</p><p>RESTful API adalah API yang mengikuti aturan atau arsitektur bernama <strong>REST (Representational State Transfer)</strong>. Sederhananya, REST memberikan seperangkat aturan tentang bagaimana client dan server saling berkomunikasi dengan baik dan terstruktur.</p><p>Dalam REST, komunikasi dilakukan menggunakan protokol <strong>HTTP</strong>, yaitu protokol yang juga digunakan ketika kita membuka sebuah website di browser. Melalui HTTP inilah client dapat meminta data, menambahkan data, mengubah data, maupun menghapus data yang ada di server.</p><p>Nah, untuk melakukan berbagai aksi tersebut, REST menggunakan beberapa <strong>HTTP Method</strong> yang mungkin pernah kalian dengar sebelumnya, seperti:</p><ul><li><strong>GET</strong> → Mengambil data</li><li><strong>POST</strong> → Menambahkan data baru</li><li><strong>PUT</strong> → Mengubah seluruh data</li><li><strong>PATCH</strong> → Mengubah sebagian data</li><li><strong>DELETE</strong> → Menghapus data</li></ul><p>Masih bingung fungsi masing-masing method tersebut? Tenang aja sobat 😆. Di bagian selanjutnya kita bakal membedah satu per satu lengkap dengan contoh implementasinya menggunakan Laravel.</p><p>Jadi jangan diskip ya, karena method-method inilah yang menjadi senjata utama saat membangun RESTful API. 🚀</p><h4>Cara kerja RESTful API</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*7d8WKgCNgNJi2KFTMMKyRg.png" /></figure><h3>Cara Kerja RESTful API</h3><p>Sebenarnya, cara kerja RESTful API tidak jauh berbeda dengan API pada umumnya. Yang membedakan adalah bagaimana client dan server saling berkomunikasi.</p><p>Pada RESTful API, client mengirimkan request ke server menggunakan <strong>HTTP Method</strong> seperti <strong>GET</strong>, <strong>POST</strong>, <strong>PUT</strong>, <strong>PATCH</strong>, atau <strong>DELETE</strong> melalui sebuah URL tertentu.</p><p>Sebagai contoh:</p><p><a href="https://todo-app.com/api/tasks">https://todo-app.com/api/tasks</a></p><p>URL tersebut dapat diibaratkan sebagai alamat tujuan yang digunakan client untuk berkomunikasi dengan server. Ketika client mengakses URL tersebut menggunakan method tertentu, server akan mengetahui tindakan apa yang ingin dilakukan.</p><p>Setelah request diterima dan diproses, server akan mengirimkan kembali sebuah <strong>response</strong> kepada client. Response ini biasanya berbentuk data seperti:</p><ul><li>JSON (JavaScript Object Notation)</li><li>XML (Extensible Markup Language)</li><li>HTML (HyperText Markup Language)</li></ul><p>Saat ini, format yang paling sering digunakan dalam RESTful API adalah <strong>JSON</strong> karena lebih ringan, mudah dibaca, dan mudah diproses oleh berbagai bahasa pemrograman.</p><p>“Aduhh kak, JSON itu apalagi?” 😵</p><p>Santai sobat, nanti kita akan membahasnya secara lebih detail lengkap dengan contoh implementasinya di Laravel. Jadi lanjut bacanya yaa 😁</p><h3>Kerangka Kerja RESTful API</h3><p>Agar RESTful API dapat berjalan dengan baik, ada beberapa komponen utama yang harus ada. Komponen-komponen inilah yang menjadi fondasi dalam membangun sebuah RESTful API.</p><h3>1. URL (Endpoint)</h3><p>URL merupakan alamat yang digunakan client untuk mengakses suatu resource pada server.</p><p>Contoh:</p><p><a href="https://todo-app.com/api/tasks">https://todo-app.com/api/tasks</a></p><p>Dari URL tersebut, server memahami bahwa client ingin berinteraksi dengan resource <strong>tasks</strong>.</p><h3>2. HTTP Method</h3><p>HTTP Method digunakan untuk menentukan aksi yang ingin dilakukan terhadap suatu resource.</p><p>Beberapa method yang paling sering digunakan adalah:</p><ul><li><strong>GET</strong> → Mengambil data</li><li><strong>POST</strong> → Menambahkan data baru</li><li><strong>PUT</strong> → Mengubah seluruh data</li><li><strong>PATCH</strong> → Mengubah sebagian data</li><li><strong>DELETE</strong> → Menghapus data</li></ul><p>Method inilah yang menjadi ciri khas dari arsitektur REST.</p><h3>3. Request Body</h3><p>Request Body adalah data yang dikirim oleh client ke server, biasanya digunakan saat membuat atau memperbarui data.</p><p>Sebagai contoh, ketika pengguna ingin membuat tugas baru pada To Do App, client dapat mengirimkan data seperti berikut:</p><pre>{<br>    &quot;title&quot;: &quot;Mengerjakan Challenge Backend&quot;,<br>    &quot;status&quot;: &quot;pending&quot;<br>}</pre><p>Di dalam Request Body terdapat:</p><ul><li><strong>Key</strong> → Nama atau label data (title, status)</li><li><strong>Value</strong> → Isi dari data tersebut (Mengerjakan Challenge Backend, pending)</li></ul><p>Server kemudian akan membaca data tersebut dan memprosesnya sesuai kebutuhan aplikasi.</p><p>Nah, ketiga komponen di atas merupakan fondasi utama yang hampir selalu kita temui saat bekerja dengan RESTful API. Setelah memahami konsep ini, nantinya kita akan jauh lebih mudah saat mulai membuat API menggunakan Laravel. 🚀</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/627/0*YD0iYeNa0cFe_5Lo.png" /><figcaption>Contoh request body</figcaption></figure><h3>4. Headers</h3><p>Selain Request Body, ada juga yang namanya <strong>Headers</strong>. Headers merupakan informasi tambahan yang dikirim bersamaan dengan request atau response.</p><p>Sama seperti Request Body, Headers juga terdiri dari pasangan <strong>key</strong> dan <strong>value</strong>. Bedanya, jika Request Body berisi data utama yang ingin dikirim, maka Headers berisi informasi pendukung yang membantu server memahami bagaimana request tersebut harus diproses.</p><p>Salah satu contoh Header yang paling sering digunakan adalah:</p><pre>Content-Type: application/json</pre><p>Pada contoh di atas:</p><ul><li><strong>Content-Type</strong> merupakan key</li><li><strong>application/json</strong> merupakan value</li></ul><p>Header tersebut memberi tahu server bahwa data yang dikirim pada Request Body menggunakan format <strong>JSON</strong>.</p><p>Selain Content-Type, masih banyak Header lain yang sering digunakan dalam pengembangan API, misalnya:</p><pre>Authorization: Bearer token123<br>Accept: application/json</pre><ul><li><strong>Authorization</strong> digunakan untuk mengirimkan informasi autentikasi atau token akses.</li><li><strong>Accept</strong> digunakan untuk memberi tahu format response yang diharapkan oleh client.</li></ul><p>Untuk saat ini, yang perlu kalian pahami adalah bahwa Headers berfungsi sebagai “catatan tambahan” yang membantu client dan server saling memahami bagaimana data harus dikirim dan diproses.</p><p>Tenang sobat, saat nanti kita mulai praktik membuat RESTful API menggunakan Laravel, kalian akan sering bertemu dengan Headers dan langsung melihat bagaimana penggunaannya di dunia nyata. 🚀</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/627/0*S2wq28CWLt4ckIK-.png" /><figcaption>Contoh headers</figcaption></figure><h3>5. Response</h3><p>Jika Request adalah data yang dikirim dari client ke server, maka <strong>Response</strong> adalah data balasan yang dikirim oleh server kepada client setelah request berhasil diproses.</p><p>Response biasanya berisi informasi seperti:</p><ul><li>Status request berhasil atau gagal</li><li>Pesan (message)</li><li>Data yang diminta oleh client</li></ul><p>Format response yang paling sering digunakan saat ini adalah:</p><ul><li><strong>JSON (JavaScript Object Notation)</strong></li><li><strong>XML (eXtensible Markup Language)</strong></li><li><strong>HTML (HyperText Markup Language)</strong></li></ul><p>Namun dalam pengembangan RESTful API modern, format <strong>JSON</strong> jauh lebih populer karena lebih ringan, mudah dibaca, dan mudah diproses oleh berbagai bahasa pemrograman.</p><p>Sebagai contoh, ketika client meminta daftar user dari server, server dapat mengirimkan response seperti berikut:</p><pre>{<br>  &quot;status&quot;: &quot;success&quot;,<br>  &quot;data&quot;: [<br>    {<br>      &quot;id&quot;: 1,<br>      &quot;name&quot;: &quot;Faris&quot;,<br>      &quot;email&quot;: &quot;faris@mail.com&quot;,<br>      &quot;role&quot;: &quot;user&quot;<br>    },<br>    {<br>      &quot;id&quot;: 2,<br>      &quot;name&quot;: &quot;Naja&quot;,<br>      &quot;email&quot;: &quot;naja@mail.com&quot;,<br>      &quot;role&quot;: &quot;admin&quot;<br>    },<br>    {<br>      &quot;id&quot;: 3,<br>      &quot;name&quot;: &quot;Mel&quot;,<br>      &quot;email&quot;: &quot;mel@mail.com&quot;,<br>      &quot;role&quot;: &quot;user&quot;<br>    },<br>    {<br>      &quot;id&quot;: 4,<br>      &quot;name&quot;: &quot;Ima&quot;,<br>      &quot;email&quot;: &quot;ima@mail.com&quot;,<br>      &quot;role&quot;: &quot;admin&quot;<br>    }<br>  ]<br>}</pre><pre>&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;<br>&lt;response&gt;<br>  &lt;status&gt;success&lt;/status&gt;<br>  &lt;data&gt;<br>    &lt;user&gt;<br>      &lt;id&gt;1&lt;/id&gt;<br>      &lt;name&gt;Faris&lt;/name&gt;<br>      &lt;email&gt;faris@mail.com&lt;/email&gt;<br>      &lt;role&gt;user&lt;/role&gt;<br>    &lt;/user&gt;<br>    &lt;user&gt;<br>      &lt;id&gt;2&lt;/id&gt;<br>      &lt;name&gt;Naja&lt;/name&gt;<br>      &lt;email&gt;naja@mail.com&lt;/email&gt;<br>      &lt;role&gt;admin&lt;/role&gt;<br>    &lt;/user&gt;<br>    &lt;user&gt;<br>      &lt;id&gt;3&lt;/id&gt;<br>      &lt;name&gt;Mel&lt;/name&gt;<br>      &lt;email&gt;mel@mail.com&lt;/email&gt;<br>      &lt;role&gt;user&lt;/role&gt;<br>    &lt;/user&gt;<br>    &lt;user&gt;<br>      &lt;id&gt;4&lt;/id&gt;<br>      &lt;name&gt;Ima&lt;/name&gt;<br>      &lt;email&gt;ima@mail.com&lt;/email&gt;<br>      &lt;role&gt;admin&lt;/role&gt;<br>    &lt;/user&gt;<br>  &lt;/data&gt;<br>&lt;/response&gt;</pre><p>Dari response tersebut kita dapat mengetahui bahwa request berhasil diproses karena nilai <strong>status</strong> adalah <em>success</em>, dan data yang dikembalikan berupa daftar user.</p><h3>Method RESTful API</h3><p>Nah, sekarang kita masuk ke bagian yang paling sering digunakan ketika membuat RESTful API, yaitu <strong>HTTP Method</strong>.</p><p>Method dapat diibaratkan sebagai perintah yang dikirim client kepada server. Melalui method inilah server mengetahui tindakan apa yang ingin dilakukan terhadap suatu data.</p><p>Method yang paling umum digunakan adalah:</p><ul><li>GET</li><li>POST</li><li>PUT</li><li>PATCH</li><li>DELETE</li></ul><p>Yuk kita bahas satu per satu 🚀</p><h3>GET</h3><p>Method <strong>GET</strong> digunakan untuk mengambil atau membaca data dari server.</p><p>Misalnya pada To Do App, kita ingin melihat seluruh daftar tugas yang tersimpan.</p><pre>GET /api/tasks</pre><p>Server kemudian akan mengirimkan daftar task yang tersedia.</p><p>GET juga dapat menggunakan <strong>parameter</strong> untuk melakukan filtering data tertentu.</p><p>Contohnya:</p><pre>GET /api/tasks?status=completed</pre><p>Artinya kita hanya ingin mengambil task yang statusnya sudah selesai (<em>completed</em>).</p><p>Jadi parameter digunakan untuk mempersempit atau memfilter data yang ingin kita ambil.</p><h3>POST</h3><p>Method <strong>POST</strong> digunakan untuk membuat atau menambahkan data baru ke database.</p><p>Pada method ini biasanya kita mengirimkan data melalui Request Body.</p><p>Contoh:</p><pre>POST /api/tasks</pre><p>Body:</p><pre>{<br>    &quot;title&quot;: &quot;Mengerjakan Challenge Backend&quot;,<br>    &quot;status&quot;: &quot;pending&quot;<br>}</pre><p>Data tersebut akan dikirim ke server dan disimpan sebagai task baru.</p><p>Singkatnya:</p><p><strong>POST = Membuat Data Baru</strong></p><h3>PUT / PATCH</h3><p>Method <strong>PUT</strong> dan <strong>PATCH</strong> digunakan untuk memperbarui data yang sudah ada.</p><p>Contoh:</p><pre>PUT /api/tasks/21</pre><p>Body:</p><pre>{<br>    &quot;title&quot;: &quot;Challenge Backend Selesai&quot;,<br>    &quot;status&quot;: &quot;completed&quot;<br>}</pre><p>Artinya kita ingin mengubah data task dengan ID 21.</p><p>Lalu apa perbedaan PUT dan PATCH?</p><h3>PUT</h3><p>Digunakan untuk mengganti atau memperbarui seluruh data resource.</p><pre>PUT /api/tasks/21</pre><p>Biasanya seluruh field dikirim ulang ke server.</p><h3>PATCH</h3><p>Digunakan untuk memperbarui sebagian data saja.</p><pre>PATCH /api/tasks/21</pre><p>Body:</p><pre>{<br>    &quot;status&quot;: &quot;completed&quot;<br>}</pre><p>Karena hanya status yang berubah, kita tidak perlu mengirim seluruh data task.</p><p>Singkatnya:</p><ul><li><strong>PUT → Update seluruh data</strong></li><li><strong>PATCH → Update sebagian data</strong></li></ul><h3>DELETE</h3><p>Method <strong>DELETE</strong> digunakan untuk menghapus data dari database.</p><p>Contoh:</p><pre>DELETE /api/tasks/21</pre><p>Artinya server akan menghapus task yang memiliki ID 21.</p><p>Biasanya ID resource dituliskan langsung pada endpoint agar server mengetahui data mana yang harus dihapus.</p><p>Singkatnya:</p><p><strong>DELETE = Menghapus Data</strong></p><p>Yeayyy, akhirnya kita sudah selesai membahas teori dasar RESTful API.</p><p>Mulai dari pengertian RESTful API, cara kerjanya, komponen-komponennya, hingga berbagai HTTP Method yang akan sering kalian gunakan sebagai Backend Developer.</p><p>Selanjutnya kita akan masuk ke bagian yang lebih seru, yaitu <strong>mengimplementasikan RESTful API menggunakan Laravel 12</strong>. Siapkan editor kalian, karena setelah ini kita akan mulai ngoding! 🚀</p><h3>Setup-setup</h3><pre>//clone repo dulu <br>git clone -b pertemuan-9 https://github.com/amccamikom/amcc-web-backend-2025.git .<br><br>//instal<br>composer install<br><br>//membuat env<br>cp .env.example .env<br><br>//generate key<br>php artisan key:generate<br><br>//migration<br>php artisan migrate<br></pre><h3>Implementasi RESTful API di Laravel</h3><p>Gimana sobat? Sampai sini sudah mulai kebayang kan apa itu API, RESTful API, request, response, dan berbagai method yang digunakan? 😎</p><p>Nah, teori tanpa praktik rasanya kurang afdol yaa. Sekarang saatnya kita masuk ke bagian yang paling seru, yaitu <strong>membuat RESTful API menggunakan Laravel 12</strong>. 🚀</p><p>Pada sesi ini kita akan belajar bagaimana membuat endpoint API, mengelola data menggunakan berbagai HTTP Method, hingga mengembalikan response dalam format JSON yang rapi dan terstruktur.</p><p>Yuk langsung gaskeun! 🏃‍♂️💨</p><h3>Persiapan</h3><p>Sebelum memulai praktikum, pastikan beberapa kebutuhan berikut sudah tersedia di perangkat kalian:</p><ul><li>Visual Studio Code</li><li>Laragon yang sudah terinstall dan menyediakan PHP, MySQL, serta Apache/Nginx</li><li>Project Laravel dari pertemuan sebelumnya</li><li>Postman untuk melakukan testing API</li><li>Database kalian (todo app)</li></ul><p>Jika belum memiliki Postman, silakan download terlebih dahulu melalui website resminya atau gunakan versi web yang tersedia.</p><h3>Setup API di Laravel Menggunakan Eloquent API Resources</h3><p>Sebenarnya membuat API di Laravel itu cukup sederhana. Secara umum kita hanya membutuhkan:</p><ul><li>Route</li><li>Controller</li><li>Model</li></ul><p>Namun Laravel menyediakan fitur tambahan yang membuat struktur response API menjadi jauh lebih rapi dan mudah dikelola, yaitu <strong>Eloquent API Resources</strong>.</p><h3>Apa itu Eloquent API Resources?</h3><p>Eloquent API Resources adalah fitur bawaan Laravel yang digunakan untuk mengubah data hasil query menjadi format JSON yang lebih terstruktur sebelum dikirim ke client.</p><p>Tanpa API Resource, data dari database biasanya akan langsung dikirim begitu saja ke client. Dengan API Resource, kita dapat menentukan data mana saja yang ingin ditampilkan dan bagaimana format output yang akan diterima oleh client.</p><p>Jadi bisa dibilang API Resource bertindak sebagai “penyaring” atau “formatter” sebelum data dikirimkan sebagai response API.</p><p>Kalau masih belum kebayang, tenang aja 😆. Nanti sambil praktik pasti langsung paham.</p><p>Untuk tahap awal, kita akan fokus membuat operasi CRUD pada tabel <strong>users</strong> terlebih dahulu.</p><h3>1. Membuat API Resource</h3><p>Buat Resource untuk User menggunakan perintah berikut:</p><pre>php artisan make:resource UserResource</pre><p>Setelah berhasil dijalankan, Laravel akan membuat file:</p><pre>app/Http/Resources/UserResource.php</pre><p>File inilah yang nantinya digunakan untuk mengatur format response data user yang dikirimkan oleh API.</p><h3>2. Membuat Controller API</h3><p>Karena model <strong>User</strong> sudah tersedia secara otomatis ketika project Laravel dibuat, kita hanya perlu membuat Controller.</p><p>Jalankan perintah berikut:</p><pre>php artisan make:controller API/UserController</pre><p>File controller akan dibuat pada lokasi:</p><pre>app/Http/Controllers/API/UserController.php</pre><p>Controller ini nantinya akan berisi seluruh logic CRUD untuk data user.</p><h3>3. Membuat Route API</h3><p>Selanjutnya buka file:</p><pre>routes/api.php</pre><p>Kemudian tambahkan kode berikut:</p><pre>use App\Http\Controllers\API\UserController;<br>use Illuminate\Support\Facades\Route;</pre><pre>Route::apiResource(&#39;/users&#39;, UserController::class);</pre><p>Pada kode di atas, kita menggunakan method <strong>apiResource()</strong> yang secara otomatis membuat seluruh route CRUD untuk resource users.</p><p>Artinya kita tidak perlu mendefinisikan route satu per satu secara manual.</p><h3>4. Melihat Daftar Route yang Dibuat</h3><p>Untuk memastikan route berhasil dibuat, jalankan perintah berikut:</p><pre>php artisan route:list</pre><p>Jika berhasil, Laravel akan menampilkan daftar endpoint seperti:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/627/0*u3SfxQ9VStP0FlnE.png" /><figcaption>Route list users endpoint</figcaption></figure><p>Nahh, hanya dengan satu baris apiResource(), Laravel langsung membuat seluruh endpoint CRUD yang kita butuhkan. Praktis banget kan? 😆</p><p>Pada langkah berikutnya kita akan mulai mengisi <strong>UserResource</strong> dan <strong>UserController</strong> agar endpoint-endpoint tersebut benar-benar bisa mengakses data dari database dan mengembalikan response JSON yang rapi. 🚀</p><p>Kalau tidak memakai <em>apiResource</em> maka kita perlu membuat route endpoint users satu persatu. Tentuu capek kan kalau begini😰</p><pre>Route::get(&#39;/users&#39;, [UserController::class,&#39;index&#39;]);<br>Route::get(&#39;/users/{id}&#39;, [UserController::class,&#39;show&#39;]);<br>Route::post(&#39;/users&#39;, [UserController::class,&#39;store&#39;]);<br>Route::put(&#39;/users/{id}&#39;, [UserController::class,&#39;update&#39;]);<br>Route::delete(&#39;/users/{id}&#39;, [UserController::class,&#39;destroy&#39;]);</pre><h3>Mendapatkan Semua Data Task dengan Method GET</h3><p>Sekarang kita akan membuat endpoint pertama pada RESTful API kita, yaitu endpoint untuk menampilkan seluruh data task yang tersimpan di database.</p><p>Karena sebelumnya kita menggunakan apiResource(), maka ketika client mengirim request menggunakan method <strong>GET</strong> ke endpoint /api/tasks, Laravel akan secara otomatis menjalankan function index() yang ada pada TaskController.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/369/0*uOugYhMzKIq8qPfI.png" /><figcaption>Test dengan postman</figcaption></figure><h3>1. Mengatur Output JSON Menggunakan TaskResource</h3><p>Sebelum mengambil data dari database, kita akan membuat format output JSON terlebih dahulu menggunakan Resource.</p><p>Buka file TaskResource.php, kemudian ubah menjadi seperti berikut:</p><pre>namespace App\Http\Resources;</pre><pre>use Illuminate\Http\Request;<br>use Illuminate\Http\Resources\Json\JsonResource;</pre><pre>class TaskResource extends JsonResource<br>{<br>    /**<br>     * Mengubah data menjadi format JSON<br>     */<br>    public function toArray(Request $request): array<br>    {<br>        return [<br>            &#39;id&#39;          =&gt; $this-&gt;id,<br>            &#39;title&#39;       =&gt; $this-&gt;title,<br>            &#39;description&#39; =&gt; $this-&gt;description,<br>            &#39;status&#39;      =&gt; $this-&gt;status,<br>            &#39;deadline&#39;    =&gt; $this-&gt;deadline,<br>            &#39;created_at&#39;  =&gt; $this-&gt;created_at,<br>            &#39;updated_at&#39;  =&gt; $this-&gt;updated_at,<br>        ];<br>    }<br>}</pre><p>Resource digunakan untuk menentukan data apa saja yang akan ditampilkan kepada client.</p><p>Dengan menggunakan Resource, kita dapat menyembunyikan field yang tidak diperlukan dan membuat format response API menjadi lebih rapi dan konsisten.</p><h3>2. Membuat Function Index pada TaskController</h3><p>Selanjutnya buka file TaskController.php.</p><p>Karena kita menggunakan apiResource(), maka endpoint GET untuk mengambil seluruh data akan menggunakan function index().</p><p>Tambahkan kode berikut:</p><pre>namespace App\Http\Controllers\API;</pre><pre>use App\Http\Controllers\Controller;<br>use App\Http\Resources\TaskResource;<br>use App\Models\Task;</pre><pre>class TaskController extends Controller<br>{<br>    /**<br>     * Menampilkan seluruh data task<br>     */<br>    public function index()<br>    {<br>        $tasks = Task::all();</pre><pre>        return TaskResource::collection($tasks);<br>    }<br>}</pre><p>Mari kita pahami kode di atas:</p><pre>$tasks = Task::all();</pre><p>Kode tersebut digunakan untuk mengambil seluruh data yang terdapat pada tabel tasks menggunakan <strong>Eloquent ORM</strong>.</p><p>Eloquent merupakan fitur ORM (Object Relational Mapping) bawaan Laravel yang memungkinkan kita berinteraksi dengan database menggunakan syntax PHP yang lebih mudah dibaca dibandingkan menulis query SQL secara manual.</p><p>Selanjutnya:</p><pre>return TaskResource::collection($tasks);</pre><p>Kode tersebut digunakan untuk mengubah seluruh data task yang telah diambil menjadi format JSON menggunakan TaskResource.</p><p>Dengan begitu, data yang dikirimkan ke client akan mengikuti format yang telah kita tentukan sebelumnya.</p><h3>3. Menjalankan dan Menguji API</h3><p>Setelah semua kode selesai dibuat, jalankan project Laravel menggunakan perintah:</p><pre>php artisan serve</pre><p>Kemudian buka aplikasi Postman.</p><p>Lakukan request berikut:</p><pre>GET http://127.0.0.1:8000/api/tasks</pre><p>Karena method GET digunakan untuk mengambil data, maka kita tidak perlu mengirimkan Request Body.</p><p>Klik tombol <strong>Send</strong> untuk mengirim request ke server.</p><h3>4. Hasil Response</h3><p>Jika berhasil, server akan mengembalikan response seperti berikut:</p><pre>{<br>    &quot;data&quot;: [<br>        {<br>            &quot;id&quot;: 1,<br>            &quot;title&quot;: &quot;Belajar RESTful API&quot;,<br>            &quot;description&quot;: &quot;Mempelajari konsep dasar RESTful API&quot;,<br>            &quot;status&quot;: &quot;pending&quot;,<br>            &quot;deadline&quot;: &quot;2026-06-30&quot;,<br>            &quot;created_at&quot;: &quot;2026-06-01T10:00:00.000000Z&quot;,<br>            &quot;updated_at&quot;: &quot;2026-06-01T10:00:00.000000Z&quot;<br>        },<br>        {<br>            &quot;id&quot;: 2,<br>            &quot;title&quot;: &quot;Mengerjakan Challenge Backend&quot;,<br>            &quot;description&quot;: &quot;Menyelesaikan challenge pertemuan 9&quot;,<br>            &quot;status&quot;: &quot;completed&quot;,<br>            &quot;deadline&quot;: &quot;2026-06-15&quot;,<br>            &quot;created_at&quot;: &quot;2026-06-02T08:30:00.000000Z&quot;,<br>            &quot;updated_at&quot;: &quot;2026-06-02T08:30:00.000000Z&quot;<br>        }<br>    ]<br>}</pre><p>Pada response di atas terdapat beberapa bagian penting:</p><ul><li><strong>data</strong> → kumpulan data task yang dikirimkan oleh server.</li><li><strong>id</strong> → identitas unik dari setiap task.</li><li><strong>title</strong> → judul task.</li><li><strong>description</strong> → deskripsi task.</li><li><strong>status</strong> → status pengerjaan task.</li><li><strong>deadline</strong> → batas waktu pengerjaan task.</li><li><strong>created_at</strong> → waktu task dibuat.</li><li><strong>updated_at</strong> → waktu terakhir task diperbarui.</li></ul><p>Karena kita menggunakan TaskResource, maka hanya field yang kita definisikan pada Resource saja yang akan ditampilkan kepada client.</p><h3>Kesimpulan</h3><p>Selamat! 🎉</p><p>Kita telah berhasil membuat endpoint RESTful API pertama menggunakan method <strong>GET</strong>.</p><p>Pada praktikum ini kita mempelajari beberapa hal penting:</p><ul><li>Menggunakan <strong>Eloquent ORM</strong> untuk mengambil data dari database.</li><li>Menggunakan <strong>TaskResource</strong> untuk mengatur format response JSON.</li><li>Membuat function <strong>index()</strong> pada controller.</li><li>Melakukan testing endpoint menggunakan Postman.</li></ul><p>Sekarang client sudah dapat mengambil seluruh data task yang tersimpan pada database melalui endpoint:</p><pre>GET /api/tasks</pre><p>Pada langkah berikutnya kita akan membuat endpoint menggunakan method <strong>POST</strong> untuk menambahkan task baru ke dalam database. 🚀</p><h3>Membuat Task Baru dengan Method POST</h3><p>Setelah berhasil mengambil seluruh data task menggunakan method GET, sekarang kita akan membuat endpoint untuk menambahkan task baru ke dalam database menggunakan method <strong>POST</strong>.</p><p>Method POST digunakan ketika client ingin mengirim data ke server untuk membuat resource baru.</p><p>Karena kita menggunakan apiResource(), maka request POST ke endpoint /api/tasks akan otomatis menjalankan function store() pada TaskController.</p><h3>1. Menyiapkan Model Task</h3><p>Sebelum membuat data baru menggunakan Eloquent, kita perlu menentukan field mana saja yang boleh diisi secara massal (<em>Mass Assignment</em>).</p><p>Buka file Task.php, kemudian tambahkan property $fillable.</p><pre>namespace App\Models;</pre><pre>use Illuminate\Database\Eloquent\Model;</pre><pre>class Task extends Model<br>{<br>    protected $fillable = [<br>        &#39;title&#39;,<br>        &#39;description&#39;,<br>        &#39;status&#39;,<br>        &#39;deadline&#39;,<br>    ];<br>}</pre><p>Property $fillable digunakan untuk menentukan kolom yang boleh diisi menggunakan method create().</p><h3>2. Membuat Function Store pada TaskController</h3><p>Selanjutnya buka file TaskController.php, kemudian tambahkan function store() berikut.</p><pre>namespace App\Http\Controllers\API;</pre><pre>use App\Models\Task;<br>use Illuminate\Http\Request;<br>use App\Http\Controllers\Controller;</pre><pre>class TaskController extends Controller<br>{<br>    /**<br>     * Menyimpan task baru<br>     */<br>    public function store(Request $request)<br>    {<br>        Task::create([<br>            &#39;title&#39;       =&gt; $request-&gt;title,<br>            &#39;description&#39; =&gt; $request-&gt;description,<br>            &#39;status&#39;      =&gt; $request-&gt;status,<br>            &#39;deadline&#39;    =&gt; $request-&gt;deadline,<br>        ]);</pre><pre>        return response()-&gt;json([<br>            &#39;status&#39; =&gt; true,<br>            &#39;message&#39; =&gt; &#39;Task created successfully&#39;<br>        ]);<br>    }<br>}</pre><p>Pada kode di atas:</p><pre>Task::create(...)</pre><p>digunakan untuk membuat data task baru menggunakan Eloquent ORM.</p><p>Data yang dikirim oleh client melalui Request akan disimpan ke tabel tasks sesuai dengan field yang telah ditentukan pada $fillable.</p><h3>3. Melakukan Testing Menggunakan Postman</h3><p>Jalankan aplikasi Laravel terlebih dahulu.</p><pre>php artisan serve</pre><p>Kemudian buka Postman dan buat request baru.</p><h3>Endpoint</h3><pre>POST http://127.0.0.1:8000/api/tasks</pre><h3>Headers</h3><pre>Content-Type: application/json</pre><p>Header tersebut digunakan untuk memberi tahu server bahwa data yang dikirim menggunakan format JSON.</p><h3>Request Body</h3><p>Pilih menu <strong>Body → Raw → JSON</strong>, kemudian masukkan data berikut:</p><pre>{<br>    &quot;title&quot;: &quot;Mengerjakan Challenge Backend&quot;,<br>    &quot;description&quot;: &quot;Menyelesaikan challenge pertemuan 9&quot;,<br>    &quot;status&quot;: &quot;pending&quot;,<br>    &quot;deadline&quot;: &quot;2026-06-30&quot;<br>}</pre><p>Setelah itu klik tombol <strong>Send</strong>.</p><h3>4. Hasil Response</h3><p>Jika proses berhasil, maka server akan mengembalikan response seperti berikut:</p><pre>{<br>    &quot;status&quot;: true,<br>    &quot;message&quot;: &quot;Task created successfully&quot;<br>}</pre><p>Response tersebut menunjukkan bahwa data berhasil diterima dan disimpan ke dalam database.</p><h3>5. Memastikan Data Berhasil Tersimpan</h3><p>Untuk memastikan proses berhasil, buka database menggunakan phpMyAdmin atau aplikasi database lainnya.</p><p>Kemudian buka tabel tasks.</p><p>Jika berhasil, maka akan muncul data baru seperti berikut:</p><p>idtitlestatus1Mengerjakan Challenge Backendpending</p><p>Artinya task baru telah berhasil dibuat melalui RESTful API.</p><h3>Kesimpulan</h3><p>Selamat! 🎉</p><p>Kita telah berhasil membuat endpoint menggunakan method <strong>POST</strong> untuk menambahkan data baru ke database.</p><p>Pada praktikum ini kita mempelajari:</p><ul><li>Penggunaan method POST pada RESTful API.</li><li>Penggunaan $fillable pada Eloquent.</li><li>Membuat data baru menggunakan Task::create().</li><li>Melakukan testing endpoint menggunakan Postman.</li></ul><p>Sekarang API kita tidak hanya dapat membaca data, tetapi juga sudah mampu menambahkan data baru ke dalam database melalui endpoint:</p><pre>POST /api/tasks</pre><p>Pada langkah berikutnya kita akan mempelajari cara mengubah data task menggunakan method <strong>PATCH</strong>. 🚀</p><h3>Mengupdate Task Berdasarkan ID dengan Method PATCH</h3><p>Setelah berhasil membuat task baru menggunakan method POST, sekarang kita akan mempelajari cara memperbarui data task yang sudah ada menggunakan method <strong>PATCH</strong>.</p><p>Method PATCH digunakan ketika kita hanya ingin mengubah sebagian data dari sebuah resource. Berbeda dengan PUT yang biasanya digunakan untuk mengganti seluruh data, PATCH lebih cocok untuk update beberapa field saja.</p><p>Karena kita menggunakan apiResource(), maka request PATCH ke endpoint /api/tasks/{id} akan otomatis menjalankan function update() pada TaskController.</p><h3>1. Membuat Function Update pada TaskController</h3><p>Buka file TaskController.php, kemudian tambahkan function berikut:</p><pre>public function update(Request $request, string $id)<br>{<br>    $task = Task::findOrFail($id);</pre><pre>    $task-&gt;update([<br>        &#39;title&#39; =&gt; $request-&gt;title,<br>        &#39;description&#39; =&gt; $request-&gt;description,<br>        &#39;status&#39; =&gt; $request-&gt;status,<br>        &#39;deadline&#39; =&gt; $request-&gt;deadline,<br>    ]);</pre><pre>    return response()-&gt;json([<br>        &#39;status&#39; =&gt; true,<br>        &#39;message&#39; =&gt; &#39;Task updated successfully&#39;<br>    ]);<br>}</pre><p>Mari kita pahami kode di atas.</p><pre>$task = Task::findOrFail($id);</pre><p>Kode tersebut digunakan untuk mencari task berdasarkan ID yang diberikan pada URL.</p><p>Jika data ditemukan, Laravel akan mengembalikan objek task tersebut. Namun jika data tidak ditemukan, Laravel akan otomatis mengembalikan response error <strong>404 Not Found</strong>.</p><p>Selanjutnya:</p><pre>$task-&gt;update([...]);</pre><p>Kode tersebut digunakan untuk memperbarui data task yang ditemukan sebelumnya.</p><p>Karena kita menggunakan Eloquent ORM, proses update menjadi jauh lebih sederhana tanpa perlu menulis query SQL secara manual.</p><h3>2. Melakukan Testing Menggunakan Postman</h3><p>Buat request baru pada Postman.</p><h3>Endpoint</h3><pre>PATCH http://127.0.0.1:8000/api/tasks/1</pre><h3>Headers</h3><pre>Content-Type: application/json</pre><h3>Request Body</h3><pre>{<br>    &quot;title&quot;: &quot;Belajar RESTful API Laravel&quot;,<br>    &quot;description&quot;: &quot;Mempelajari RESTful API menggunakan Laravel 12&quot;,<br>    &quot;status&quot;: &quot;completed&quot;,<br>    &quot;deadline&quot;: &quot;2026-06-30&quot;<br>}</pre><p>Kemudian klik tombol <strong>Send</strong>.</p><h3>3. Hasil Response</h3><p>Jika proses update berhasil, maka server akan mengembalikan response seperti berikut:</p><pre>{<br>    &quot;status&quot;: true,<br>    &quot;message&quot;: &quot;Task updated successfully&quot;<br>}</pre><p>Selanjutnya coba cek tabel tasks pada database.</p><p>Jika berhasil, maka data task dengan ID 1 akan berubah sesuai data yang baru dikirimkan.</p><h3>Menghapus Task dengan Method DELETE</h3><p>Selain membuat dan mengubah data, RESTful API juga memungkinkan kita untuk menghapus data menggunakan method <strong>DELETE</strong>.</p><p>Karena kita menggunakan apiResource(), maka request DELETE ke endpoint /api/tasks/{id} akan otomatis menjalankan function destroy() pada controller.</p><h3>1. Membuat Function Destroy pada TaskController</h3><p>Buka file TaskController.php, kemudian tambahkan function berikut:</p><pre>public function destroy(string $id)<br>{<br>    $task = Task::findOrFail($id);</pre><pre>    $task-&gt;delete();</pre><pre>    return response()-&gt;json([<br>        &#39;status&#39; =&gt; true,<br>        &#39;message&#39; =&gt; &#39;Task successfully deleted&#39;<br>    ]);<br>}</pre><p>Mari kita pahami kode di atas.</p><pre>$task = Task::findOrFail($id);</pre><p>Digunakan untuk mencari task berdasarkan ID.</p><p>Kemudian:</p><pre>$task-&gt;delete();</pre><p>Digunakan untuk menghapus data task dari database.</p><p>Jika task berhasil ditemukan, maka data tersebut akan langsung dihapus dari tabel tasks.</p><h3>2. Melakukan Testing Menggunakan Postman</h3><p>Buat request baru pada Postman.</p><h3>Endpoint</h3><pre>DELETE http://127.0.0.1:8000/api/tasks/1</pre><p>Karena method DELETE tidak memerlukan body, kita hanya perlu mengirim request ke endpoint tersebut.</p><p>Kemudian klik tombol <strong>Send</strong>.</p><h3>3. Hasil Response</h3><p>Jika proses penghapusan berhasil, maka server akan mengembalikan response seperti berikut:</p><pre>{<br>    &quot;status&quot;: true,<br>    &quot;message&quot;: &quot;Task successfully deleted&quot;<br>}</pre><p>Selanjutnya buka tabel tasks pada database.</p><p>Jika task dengan ID 1 sudah tidak ditemukan, maka proses penghapusan berhasil dilakukan.</p><h3>Kesimpulan</h3><p>Selamat! 🎉</p><p>Pada praktikum ini kita telah berhasil mengimplementasikan operasi CRUD (Create, Read, Update, Delete) menggunakan RESTful API pada Laravel.</p><p>Beberapa hal yang telah dipelajari antara lain:</p><ul><li>Mengambil seluruh data menggunakan method GET.</li><li>Membuat data baru menggunakan method POST.</li><li>Mengubah data menggunakan method PATCH.</li><li>Menghapus data menggunakan method DELETE.</li><li>Menggunakan Eloquent ORM untuk berinteraksi dengan database.</li><li>Menggunakan API Resource untuk menghasilkan response JSON yang lebih rapi dan terstruktur.</li><li>Melakukan pengujian endpoint menggunakan Postman.</li></ul><p>Dengan memahami materi ini, kalian sudah memiliki pondasi yang sangat penting dalam pengembangan Backend menggunakan Laravel. Hampir seluruh aplikasi modern saat ini menggunakan RESTful API sebagai jembatan komunikasi antara client dan server.</p><p>Terus eksplorasi, terus belajar, dan jangan takut mencoba hal baru. 🚀</p><p><strong>AMCC — Learning by Doing, Learning by Teaching 💙</strong></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d37977c16d28" width="1" height="1" alt=""><hr><p><a href="https://medium.com/amcc-amikom/ngulik-restful-api-dari-nol-sampai-crud-jalan-d37977c16d28">Ngulik RESTful API dari Nol Sampai CRUD Jalan</a> was originally published in <a href="https://medium.com/amcc-amikom">Amikom Computer Club</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Yuk Kenalan sama Deep Learning!]]></title>
            <link>https://medium.com/amcc-amikom/yuk-kenalan-sama-deep-learning-fd8543552d7e?source=rss----fe834c257357---4</link>
            <guid isPermaLink="false">https://medium.com/p/fd8543552d7e</guid>
            <category><![CDATA[data-science]]></category>
            <category><![CDATA[deep-learning]]></category>
            <dc:creator><![CDATA[Tasya Anggraeni Firdaus]]></dc:creator>
            <pubDate>Fri, 12 Jun 2026 08:35:42 GMT</pubDate>
            <atom:updated>2026-06-12T08:35:41.098Z</atom:updated>
            <content:encoded><![CDATA[<p>Hi, Sobat AMCC<br>Rasanya sudah jauh yaa kita belajar, hari ini kita akan kenalan dengan materi yang baru nih. Seperti judulnya, hari ini kita akan berkenalan dengan deep learning. Yuk siapkan semangatt nyaa supaya lebih paham lagi belajarnyaaa!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*pCB07ZgCxAkGlNuYaRQvrw.png" /></figure><h4>Glosarium</h4><p><strong>Artificial Intelligence (AI)</strong><br>Semua upaya membuat mesin bisa berperilaku cerdas dan mengambil keputusan layaknya manusia.</p><p><strong>Machine Learning</strong><br>Cabang AI yang memungkinkan sistem belajar dari data secara otomatis tanpa diprogram aturannya secara manual.</p><p><strong>Deep Learning</strong><br>Cabang Machine Learning yang menggunakan jaringan saraf tiruan berlapis-lapis untuk belajar dari data mentah yang kompleks.</p><p><strong>Neural Network </strong><br>Struktur komputasi yang terinspirasi otak manusia, terdiri dari neuron-neuron buatan yang saling terhubung dalam lapisan-lapisan.</p><p><strong>Neuron</strong><br>Unit pemrosesan terkecil dalam neural network. Menerima input, mengalikannya dengan bobot, lalu meneruskan hasilnya ke neuron berikutnya.</p><p><strong>Layer (Lapisan)</strong><br> Kumpulan neuron dalam satu tingkatan jaringan. Terdiri dari input layer, hidden layer, dan output layer.</p><p><strong>Weight (Bobot)</strong><br>Nilai numerik pada setiap koneksi antar neuron yang menentukan seberapa kuat pengaruh satu neuron terhadap neuron lainnya.</p><p><strong>Forward Pass</strong><br>Proses data mengalir dari input layer ke output layer untuk menghasilkan prediksi.</p><p><strong>Loss / Error</strong><br>Selisih antara prediksi model dengan jawaban yang benar. Semakin kecil loss, semakin akurat model.</p><p><strong>Loss Function</strong><br>Fungsi matematika yang mengukur seberapa jauh prediksi model dari jawaban yang benar.</p><p><strong>Backpropagation</strong><br> Algoritma yang mengirim error dari output kembali ke seluruh jaringan untuk mengetahui kontribusi kesalahan tiap neuron.</p><p><strong>Gradient Descent</strong><br>Algoritma optimasi yang menyesuaikan bobot jaringan secara bertahap agar loss semakin kecil.</p><p><strong>Learning Rate</strong><br>Nilai yang menentukan seberapa besar langkah penyesuaian bobot di setiap iterasi training.</p><p><strong>Epoch</strong><br>Satu kali putaran penuh di mana model memproses seluruh data training dari awal hingga akhir.</p><p><strong>Batch Size</strong><br>Jumlah data yang diproses sebelum model melakukan satu kali update bobot.</p><p><strong>Iteration</strong><br>Satu kali update bobot. Jumlah iteration per epoch = total data ÷ batch size.</p><p><strong>Overfitting</strong><br>Kondisi di mana model terlalu hafal data training sehingga gagal memprediksi data baru dengan akurat.</p><p><strong>Early Stopping</strong><br>Teknik menghentikan proses training otomatis ketika akurasi di data validasi tidak lagi meningkat.</p><p><strong>Activation Function</strong><br> Fungsi matematis pada setiap neuron yang menentukan apakah sinyal diteruskan atau tidak, memberi kemampuan memahami pola non-linear.</p><p><strong>ReLU (Rectified Linear Unit)</strong><br>Activation function paling populer. Nilai negatif diubah jadi 0, nilai positif tetap. Default untuk hidden layer.</p><p><strong>Sigmoid</strong><br>Activation function yang menghasilkan output antara 0–1. Cocok untuk klasifikasi dua kelas (biner).</p><p><strong>Softmax</strong><br>Activation function yang mengubah output menjadi probabilitas yang jumlahnya = 1. Dipakai untuk klasifikasi banyak kelas.</p><p><strong>Tanh</strong><br>Activation function dengan output antara -1 hingga 1. Mirip sigmoid tapi lebih baik untuk beberapa kasus RNN.</p><p><strong>Leaky ReLU</strong><br>Versi ReLU yang tidak mematikan neuron sepenuhnya pada nilai negatif. Solusi untuk masalah dying ReLU.</p><p><strong>GELU</strong><br>Activation function yang dipakai di model Transformer modern seperti BERT dan GPT. Lebih smooth dari ReLU.</p><p><strong>Embedding</strong><br>Teknik mengubah token kata menjadi vektor angka berdimensi tertentu agar bisa diproses neural network.</p><p><strong>Parameter</strong><br>Semua bobot dan bias dalam model yang dipelajari selama proses training.</p><p><strong>ANN (Artificial Neural Network)</strong><br>Arsitektur neural network paling dasar dengan koneksi penuh antar neuron di setiap lapisan. Cocok untuk data tabular.</p><p><strong>CNN (Convolutional Neural Network)</strong><br>Arsitektur yang dirancang untuk data spasial seperti gambar menggunakan operasi konvolusi untuk mendeteksi pola visual.</p><p><strong>RNN (Recurrent Neural Network)</strong><br>Arsitektur untuk data berurutan seperti teks. Punya memori jangka pendek karena output langkah sebelumnya diumpan balik sebagai input berikutnya.</p><p><strong>Hidden State</strong><br>Memori internal RNN yang menyimpan informasi dari langkah-langkah sebelumnya dalam sebuah sequence.</p><p><strong>LSTM (Long Short-Term Memory)</strong><br>Versi RNN yang lebih canggih dengan gate khusus untuk mengingat atau melupakan informasi, mengatasi masalah vanishing gradient.</p><p><strong>Forget Gate</strong><br>Komponen LSTM yang memutuskan informasi mana dari memori sebelumnya yang perlu dilupakan.</p><p><strong>Input Gate</strong><br>Komponen LSTM yang memutuskan informasi baru mana yang perlu disimpan ke memori.</p><p><strong>Output Gate</strong><br>Komponen LSTM yang memutuskan informasi mana dari memori yang diteruskan ke langkah berikutnya.</p><p><strong>Transformer</strong><br>Arsitektur revolusioner yang menggunakan mekanisme self-attention untuk memproses seluruh sequence sekaligus tanpa recurrence.</p><p><strong>Self-Attention</strong><br>Mekanisme dalam Transformer yang memungkinkan setiap token menilai seberapa relevan token lain dalam sequence terhadap dirinya.</p><p><strong>LLM (Large Language Model)</strong><br>Model bahasa berskala besar berbasis Transformer. Contoh: ChatGPT, Claude, Gemini.</p><p><strong>GAN (Generative Adversarial Network)</strong><br>Arsitektur yang terdiri dari dua jaringan yang berkompetisi: Generator (membuat data palsu) dan Discriminator (membedakan asli vs palsu).</p><h4><strong>Daftar Isi</strong></h4><ol><li>Sejarah Deep Learning</li><li>Apa itu AI, Machine Learning, dan Deep Learning?</li><li>Apa itu Deep Learning?</li><li>Cara Kerja Deep Learning</li><li>Epoch dan Proses Training</li><li>Activation Function</li><li>Fungsi dan Penerapan</li><li>Jenis-Jenis Arsitektur Deep Learning</li><li>Visualisasi CNN</li><li>Praktikum</li></ol><h3>Sejarah Deep Learning</h3><p>Deep Learning bukan teknologi yang lahir tiba-tiba. Perjalanannya panjang, penuh kegagalan, dan baru benar-benar bersinar di era modern ketika data dan komputasi akhirnya memadai.</p><p><strong>1943 Neuron</strong></p><p>Warren McCulloch dan Walter Pitts memperkenalkan model matematis pertama dari neuron biologis yang merupakan buatan pertama. Ide dasar otak bekerja dari sinyal listrik sederhana yang tersambung.</p><p><strong>1958 Perceptron</strong></p><p>Frank Rosenblatt menciptakan Perceptron, algoritma pertama yang bisa belajar dari data dan ini adalah cikal bakal Neural Network. Dunia sangat antusias tapi ternyata hanya bisa selesaikan masalah sederhana.</p><p><strong>1969–1980 AI Winter Pertama</strong></p><p>Minsky dan Papert membuktikan keterbatasan Perceptron. Pendanaan riset AI anjlok drastis. Periode ini dikenal sebagai AI Winter yaitu musim dingin kecerdasan buatan.</p><p><strong>1986 Backpropagation</strong></p><p>Geoffrey Hinton, Rumelhart, dan Williams mempopulerkan algoritma backpropagation dan ini kerap kali dipopulerkan. Jaringan berlapis kini bisa belajar merupakan fondasi Deep Learning modern.</p><p><strong>1998 LeNet</strong></p><p>Yann LeCun menciptakan LeNet, jaringan saraf konvolusional (CNN) pertama yang sukses mengenali tulisan tangan angka ini juga termasuk CNN pertama. Digunakan bank untuk baca cek.</p><p><strong>2012 AlexNet</strong></p><p>AlexNet milik Geoffrey Hinton menghancurkan kompetisi ImageNet dengan selisih akurasi 10%, juga titik balik sejarah. Era modern Deep Learning dimulai. GPU mulai dipakai untuk training model.</p><p><strong>2017 Transformer</strong></p><p>Google merilis paper <em>Attention is All You Need</em>. Arsitektur Transformer lahir dan menjadi dasar semua model bahasa modern seperti BERT, GPT, hingga LLM masa kini.</p><p><strong>2022 hingga kini Era Generative AI</strong></p><p>Seperti ChatGPT, Midjourney, Stable Diffusion, Claude, dan ratusan model lainnya membawa Deep Learning ke tangan jutaan pengguna biasa. AI bukan lagi milik peneliti saja.</p><h3>Apa Bedanya AI, Machine Learning, dan Deep Learning ?</h3><p>Ini pertanyaan yang paling sering bikin bingung. Jawabannya ketiganya bukan hal yang berbeda, melainkan <strong>tiga lingkaran yang bersarang satu sama lain.</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/670/1*4OMYnU4LT-XojsbVMF9bNw.png" /><figcaption>Gambar : Perbedaan AI, Machine Learning dan Deep Learning</figcaption></figure><p>Artificial Intelligence semua sistem yang bisa berpikir Machine Learning Belajar dari data Deep Learning Jaringan sarafberlapis-lapis</p><p><strong>AI</strong> yaitu semua upaya membuat mesin bisa berperilaku cerdas termasuk program catur sederhana tahun 1950-an.</p><p><strong>Machine Learning </strong>adalah AI yang belajar dari data bukan dari aturan yang ditulis manual oleh programmer.</p><p><strong>Deep Learning</strong> adalah Machine Learning yang menggunakan jaringan saraf tiruan berlapis-lapis, mampu memproses data sangat kompleks seperti gambar, suara, dan bahasa.</p><blockquote>Analoginya: bayangkan olahraga, sepak bola, dan tendangan bebas. Tendangan bebas adalah teknik dalam sepak bola, dan sepak bola adalah cabang olahraga. Tidak bisa dibalik karena ketiganya satu keluarga.</blockquote><h3>Apa itu Deep Learning?</h3><p>Deep Learning adalah cabang Machine Learning yang terinspirasi cara kerja otak manusia. Ia menggunakan struktur berlapis yang disebut <strong>jaringan saraf tiruan (Artificial Neural Network)</strong> untuk belajar langsung dari data mentah tanpa perlu programmer menulis aturan satu per satu.</p><blockquote>“Bukan kita yang mengajari komputer aturannya. Kita beri dia data, dan dia cari aturannya sendiri.”</blockquote><h3>Kenapa disebut “Deep”?</h3><p>Kata <em>deep</em> merujuk pada <strong>banyaknya lapisan (layer)</strong> dalam jaringan. Semakin banyak lapisan, semakin kompleks pola yang bisa dipelajari. Model modern seperti GPT-4 memiliki ratusan lapisan dengan miliaran parameter.</p><h4>Yuk kita analogikan…</h4><p>Ketika kamu buka TikTok pertama kali pasti isinya random. Tapi TikTok mengamati diam-diam loh gaiss video mana yang kamu tonton sampai habis, mana yang diskip 3 detik, mana yang di-replay jam 2 malam. Dari ribuan sinyal itu, sistemnya membentuk gambaran tentang kamu loh bukan dari yang kamu bilang, tapi dari apa yang kamu lakukan.</p><p>Tidak ada karyawan TikTok yang duduk memilih video untukmu. Sistemnya <strong>belajar sendiri dari pola data jutaan pengguna</strong>. Itulah Deep Learning bekerja.</p><h3>Cara Kerja Deep Learning</h3><p>Mari kita bedah dari dalam. Sebuah neural network terdiri dari tiga bagian utama:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/667/1*kWAiQjqxC2NMinsQHvRV3w.png" /></figure><h3>Alur Kerja: 5 Langkah</h3><ol><li><strong>Forward Pass </strong>yaitu Data Masuk, Prediksi Keluar<br>Data (gambar, teks, angka) dimasukkan ke layer pertama. Setiap neuron menerima input, mengalikannya dengan <em>bobot (weight)</em>, menjumlahkan hasilnya, lalu meneruskan ke layer berikutnya. Proses ini berlangsung sampai output layer menghasilkan prediksi.</li><li><strong>Hitung Loss</strong> yaitu muncul pertanyaan seberapa salah? <br>Prediksi dibandingkan dengan jawaban benar. Selisihnya disebut <em>loss</em> atau <em>error</em>. Semakin besar loss, semakin jauh model dari kebenaran. Fungsi yang menghitung ini disebut <em>loss function</em>.</li><li><strong>Backpropagation </strong>artinya<strong> </strong>Lakan mengacak sumber kesalahan<br>Error dikirim balik dari output ke input. Setiap layer menghitung “seberapa besar kontribusi saya terhadap kesalahan ini?” menggunakan kalkulus diferensial (chain rule). Ini seperti review soal ujian cari tahu mana yang bikin salah.</li><li><strong>Update Bobot (</strong>Gradient Descent)<br>Setelah tahu seberapa salah tiap neuron, bobotnya disesuaikan menggunakan algoritma <em>Gradient Descent</em>. Bobot yang bikin salah dikurangi, yang tepat diperkuat. Learning rate menentukan seberapa besar langkah penyesuaian.</li><li><strong>Ulangi </strong><br>Satu putaran melalui seluruh data training disebut satu <em>epoch</em>. Proses ini diulang ratusan hingga ribuan epoch sampai model cukup akurat. Makin banyak data dan epoch, makin pintar modelnya.</li></ol><h3>Epoch Mengukur Proses Belajar</h3><p><strong>Epoch</strong> adalah satu kali putaran penuh di mana model melihat <em>seluruh</em> data training. Bayangkan kamu mengerjakan 1 set soal latihan dari awal sampai akhir itu satu epoch.</p><blockquote>Epoch 1 = model masih banyak salah. Epoch 100 = model sudah jauh lebih akurat. Epoch 1000 = model bisa jadi terlalu hafal data training (overfitting).</blockquote><h3>Visualisasi Akurasi per Epoch</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/735/1*7Gdly8os-WDSg31sggepXQ.png" /></figure><h4>Istilah Penting Terkait Epoch</h4><ul><li><strong>Batch Size<br></strong>Jumlah data yang diproses sebelum model update bobotnya. Epoch 1 dengan batch size 32 berarti model update sebanyak (total data / 32) kali.</li><li><strong>Iteration<br></strong>Satu kali update bobot. Jika data 1000 dan batch size 100, maka 1 epoch = 10 iteration.</li><li><strong>Overfitting<br></strong>Model terlalu banyak epoch, jadi hafal data training tapi gagal di data baru. Seperti hafal soal, bukan paham materinya.</li><li><strong>Early Stopping<br></strong>Teknik menghentikan training otomatis saat akurasi di data validasi tidak lagi meningkat.</li></ul><h3>Activation Function</h3><p>Setiap neuron melakukan dua hal: menghitung jumlah input berbobot, lalu memutuskan apakah sinyal itu perlu <em>diteruskan</em> atau tidak. Keputusan itu dibuat oleh <strong>Activation Function</strong>.</p><p>Tanpa activation function, neural network hanya akan jadi persamaan linear biasa — tidak peduli seberapa banyak layernya. Activation function yang memberi kemampuan memahami pola non-linear yang kompleks.</p><blockquote>Analogi: bayangkan lampu dimmer. Activation function menentukan apakah neuron “menyala penuh”, “menyala redup”, atau “mati” berdasarkan sinyal yang masuk.</blockquote><p><strong>ReLU</strong></p><pre>max(0, x)</pre><p>Paling populer. Nilai negatif jadi 0, nilai positif tetap. Cepat dan efisien. Default untuk hidden layer.</p><p><strong>Sigmoid</strong></p><pre>1 / (1 + e⁻ˣ)</pre><p>Output antara 0–1. Cocok untuk output binary classification (ya/tidak, spam/bukan).</p><p><strong>Softmax</strong></p><pre>eˣⁱ / Σeˣʲ</pre><p>Output berupa probabilitas yang jumlahnya = 1. Dipakai di output layer untuk multi-class classification.</p><p><strong>Tanh</strong></p><pre>tanh(x)</pre><p>Output antara -1 hingga 1. Mirip sigmoid tapi zero-centered. Lebih baik untuk beberapa kasus RNN.</p><p><strong>Leaky ReLU</strong></p><pre>max(0.01x, x)</pre><p>Versi ReLU yang tidak “mematikan” neuron sepenuhnya. Solusi masalah dying ReLU.</p><p><strong>GELU</strong></p><pre>x · Φ(x)</pre><p>Dipakai di Transformer modern seperti BERT dan GPT. Lebih smooth dari ReLU, performa lebih baik di NLP.</p><h3>Fungsi dan Penerapan Deep Learning</h3><ol><li>Computer Vision<strong><br></strong>Face ID, filter kamera, CCTV pintar, deteksi kanker dari rontgen, self-driving car.</li><li>Speech Recognition<br>Siri, Google Assistant, subtitle otomatis YouTube, voice-to-text keyboard.</li><li>Natural Language Processing<br>LLM seperti ChatGPT, Claude, Gemini. Google Translate. Analisis sentimen komentar pelanggan.</li><li>Analisis Prediktif<br>Deteksi fraud kartu kredit, prediksi harga saham, prediksi churn pelanggan, diagnosis penyakit.</li><li>Generative AI<br>Midjourney, DALL-E (gambar), Suno (musik), ElevenLabs (suara), video synthesis.</li><li>Autonomous Systems<br>Tesla Autopilot, drone navigasi otomatis, robot gudang, sistem trading algoritmik.</li></ol><blockquote>Catatan penting ya gaiss jadi ChatGPT, Claude, Gemini adalah produk yang berbeda-beda, tetapi kesemuanya dibangun di atas arsitektur yang sama yaitu Large Language Model (LLM) berbasis Transformer.</blockquote><h3>Jenis-Jenis Arsitektur Deep Learning</h3><p>Berbeda jenis data membutuhkan arsitektur berbeda. Inilah yang dimaksud jenis-jenis Deep Learning setiap arsitektur punya cara kerja dan keunikan tersendiri.</p><p><strong>ANN (Artificial Neural Network)</strong></p><p>Arsitektur paling dasar. Semua neuron di satu layer terhubung ke semua neuron di layer berikutnya (fully connected). Cocok untuk data tabular sederhana seperti spreadsheet.</p><p><strong>Hal unik: </strong>Fondasi dari semua arsitektur lainnya. Jika data kamu berupa angka-angka terstruktur (umur, pendapatan, skor), ANN biasanya sudah cukup.</p><p>Dipakai untuk: prediksi harga rumah, klasifikasi nasabah kredit, rekomendasi produk sederhana.</p><p><strong>CNN (Convolutional Neural Network)</strong></p><p>Dirancang khusus untuk data spasial seperti gambar. Menggunakan operasi konvolusi filter kecil yang meluncurdi atas gambar untuk mendeteksi pola tepi, tekstur, bentuk, hingga objek lengkap.</p><p>Parameter sharing satu filter digunakan di seluruh gambar, bukan tiap piksel punya bobot sendiri. Ini yang membuat CNN jauh lebih efisien dari ANN untuk gambar.</p><p>Dipakai untuk pengenalan wajah, deteksi objek (YOLO), klasifikasi gambar medis, filter AR.</p><p><strong>RNN (Recurrent Neural Network)</strong></p><p>Dirancang untuk data berurutan (sequential). Berbeda dari ANN/CNN, RNN punya memori output dari langkah sebelumnya diumpan balik sebagai input langkah berikutnya. Memiliki hidden state yang bertindak sebagai memori jangka pendek. Bisa memproses input dengan panjang yang bervariasi. Digunakan untuk prediksi time series, teks generasi sederhana, analisis sentimen.</p><p><strong>LSTM (Long Short-Term Memory)</strong></p><p>Versi lebih canggih dari RNN. Mengatasi masalah utama RNN vanishing gradient di mana informasi dari jauh ke belakang “terlupakan”. LSTM punya gate khusus untuk mengingat atau melupakan informasi seperti forget gate (apa yang dilupakan), input gate (apa yang diingat baru), output gate (apa yang diteruskan). Ini yang bikin LSTM bisa “ingat” konteks panjang. Biasanya digunakan untuk terjemahan mesin, prediksi saham jangka panjang, subtitling otomatis.</p><p><strong>TransformerTransformer</strong></p><p>Revolusi terbesar dalam Deep Learning modern. Diperkenalkan Google 2017 lewat paper Attention is All You Need Tidak menggunakan recurrence seperti RNN, tapi mekanisme <em>self-attention</em> yang memproses seluruh sequence sekaligus.</p><p>Self-attention memungkinkan setiap token melihat semua token lain sekaligus dan menilai seberapa relevan tiap kata terhadap kata lainnya. Inilah yang membuat ChatGPT, Claude, dan Gemini bisa memahami konteks panjang.</p><p>Digunakan untuk semua LLM modern seperti GPT, Claude, Gemini, BERT, terjemahan, code generation, image generation (Stable Diffusion).</p><p><strong>GAN (Generative Adversarial Network)</strong></p><p>Dua jaringan saling berkompetisi seperti Generator yang menciptakan data palsu dan Discriminator yang mendeteksi mana asli mana palsu. Keduanya terus saling mengalahkan hingga Generator menghasilkan output yang sangat realistis.</p><p>Konsep adversarial dua model berlawanan dalam satu sistem training. Ibaratnya pencetak uang palsu vs polisi, keduanya makin jago seiring waktu.</p><p>Digunakan untuk deepfake, generasi gambar wajah sintetis, augmentasi data medis, style transfer foto.</p><p><strong>AutoencoderAutoencoder</strong></p><p>Jaringan yang belajar mengkompresi data ke representasi yang lebih kecil (encoding), lalu merekonstruksinya kembali (decoding). Tidak membutuhkan label belajar secara unsupervised.</p><p>Representasi tengah atau biasa disebut bottleneck/latent space akan memaksa model memahami fitur-fitur paling penting dari data dan representasi ini bisa digunakan untuk tugas lain.</p><p>Dipakai untuk mendeteksi anomali, denoising gambar, kompresi data, rekomendasi sistem.</p><h3>Visualisasi CNN — Bagaimana Gambar Diproses</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/654/1*OSXrILXyLDZYPpiRhdWaSA.png" /></figure><p>CNN memproses gambar secara bertahap, dari fitur sederhana di lapisan awal hingga objek kompleks di lapisan akhir.</p><p>Lapisan konvolusi di awal mendeteksi tepi dan warna. Makin dalam, makin abstrak dari bentuk sederhana ke bagian tubuh, hingga akhirnya mengenali objek secara keseluruhan. MaxPooling di setiap tahap menyederhanakan representasi tanpa kehilangan informasi penting.</p><h3>Praktikum RNN untuk Analisis Sentimen Teks</h3><p>Nah sampai juga di sesi ini ya gaisss. Praktikum kita hari ini akan membangun model <strong>RNN (Recurrent Neural Network)</strong> untuk mengklasifikasikan sentimen review film apakah sebuah review bernada <em>positif</em> atau <em>negatif</em>. Dataset yang dipakai adalah <strong>IMDb Movie Reviews </strong>tersedia di TensorFlow. Selamat praktikum gaiss!</p><h3>Selamat Belajar Deep Learning gaiss!</h3><p>Deep Learning bukan ilmu yang harus ditakuti loh gaisss. Mulai dari konsep, bangun intuisi, lalu praktik itu yang kita gunakan setiap harii soo semangat belajarnyaa yaaaa!</p><p>#AMCC#DeepLearning#DataScience#MachineLearning#NeuralNetwork#CNN#AI#Python#TensorFlow</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=fd8543552d7e" width="1" height="1" alt=""><hr><p><a href="https://medium.com/amcc-amikom/yuk-kenalan-sama-deep-learning-fd8543552d7e">Yuk Kenalan sama Deep Learning!</a> was originally published in <a href="https://medium.com/amcc-amikom">Amikom Computer Club</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Smart Lab & Server Room Protection System with Node-RED]]></title>
            <link>https://medium.com/amcc-amikom/smart-lab-server-room-protection-system-with-node-red-369f84333978?source=rss----fe834c257357---4</link>
            <guid isPermaLink="false">https://medium.com/p/369f84333978</guid>
            <category><![CDATA[node-red]]></category>
            <category><![CDATA[hardware-software]]></category>
            <category><![CDATA[hardware]]></category>
            <category><![CDATA[software]]></category>
            <dc:creator><![CDATA[GDE AGUNG ANANDA CANDRA DINATA 24.11.6174]]></dc:creator>
            <pubDate>Fri, 12 Jun 2026 03:46:03 GMT</pubDate>
            <atom:updated>2026-06-12T08:02:20.916Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*uaWWJCxCfx7rzqBUk0wd8Q.png" /></figure><p><strong>Halo IoT Gengs! 👋</strong></p><p>Selamat datang kembali pada modul pelatihan #9 divisi Internet Of Things AMCC! Setelah di pertemuan sebelumnya kita berkenalan dengan Node-RED, sekarang saatnya kita naik level dengan mengaplikasikannya pada <em>study case</em> yang lebih menantang dan bernuansa <em>Industrial Grade</em>.</p><p>Pada pertemuan ke-9 ini, kita akan membangun <strong>Smart Lab &amp; Server Room Protection System</strong>. Kita akan menggabungkan fungsi keamanan akses pintu (<em>Security</em>) dengan sistem tanggap darurat lingkungan (<em>Safety</em>) ke dalam satu <em>dashboard</em> Node-RED yang terpusat. <em>Buckle up, and let’s dive in!</em></p><p><strong>Table of Contents</strong></p><p>· <a href="#8ab4">Let’s Talk About Smart Lab Protection</a><br>· <a href="#9a26">Apa itu Smart Lab &amp; Server Room Protection?</a><br>· <a href="#1a75">Implementasi IoT dalam Laboratorium dan Ruang Server</a><br>· <a href="#4580">Keuntungan Implementasi IoT dalam Lab dan Ruang Server</a><br>· <a href="#23ff">Skenario &amp; Logika Sistem</a><br>· <a href="#5af6">Hardware Time: Rangkaian Sensor &amp; ESP32</a><br>· <a href="#3704">Library Arduino IDE</a><br>· <a href="#fae5">The Code</a><br>· <a href="#6793">Building the Dashboard</a><br>· <a href="#eb70">Penutup</a></p><h3>Let’s Talk About Smart Lab Protection</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*IU1JdQmxgxcyiFbDThfUWQ.png" /></figure><p>Di dunia nyata, ruangan seperti laboratorium atau ruang <em>server</em> menyimpan aset yang sangat berharga. Ruangan ini memiliki dua ancaman utama:</p><ol><li><strong>Akses Tidak Sah:</strong> Orang yang tidak berkepentingan masuk dan merusak atau mencuri data/alat.</li><li><strong>Kondisi Lingkungan Ekstrem:</strong> Ruang <em>server</em> sangat rentan terhadap <em>overheat</em> (suhu panas berlebih) dan risiko kebakaran (kebocoran gas/asap).</li></ol><p>Oleh karena itu, kita butuh sebuah sistem terpusat yang bisa menangani kedua masalah ini sekaligus, dan Node-RED adalah “The Engine” yang sempurna untuk memantau semuanya secara <em>real-time</em>.</p><h3><strong>Apa itu Smart Lab &amp; Server Room Protection?</strong></h3><p>Smart Lab &amp; Server Room Protection adalah konsep pengamanan dan pemantauan ruangan kritis yang mengintegrasikan teknologi Internet of Things (IoT) untuk menjaga keselamatan aset fisik maupun data. Dengan menggunakan teknologi seperti mikrokontroler, pembaca kartu identitas (RFID), sensor lingkungan, dan aktuator, pengelola IT atau laboran dapat memantau kondisi ruangan, mengatur hak akses, dan merespons keadaan darurat secara <em>real-time</em> dari satu <em>dashboard</em> terpusat.</p><h3><strong>Implementasi IoT dalam Laboratorium dan Ruang Server</strong></h3><p>Implementasi IoT dalam ruang <em>server</em> atau laboratorium melibatkan penggunaan berbagai sensor dan aktuator yang dapat mengumpulkan serta mengeksekusi data secara otomatis. Berikut adalah beberapa aspek penting dari implementasi IoT dalam ruangan kritis:</p><ul><li><strong>Sistem Keamanan Akses (Access Control):</strong> Mengganti kunci mekanis konvensional dengan sistem pintar berbasis RFID. Hanya staf atau mahasiswa yang memiliki kartu terdaftar yang dapat membuka pintu. Setiap upaya akses, baik yang berhasil maupun ditolak, akan tercatat dalam log sistem.</li><li><strong>Monitoring Suhu dan Kelembapan:</strong> Perangkat keras seperti <em>server</em> dan alat ukur laboratorium sangat sensitif terhadap panas. Sensor IoT dipasang untuk memantau suhu ruangan secara <em>real-time</em>. Jika suhu AC tidak stabil dan ruangan mulai memanas (<em>overheat</em>), admin akan langsung menerima peringatan.</li><li><strong>Deteksi Gas Berbahaya dan Asap:</strong> Kabel <em>server</em> yang <em>overload</em> atau bahan kimia di laboratorium memiliki risiko memicu korsleting hingga kebakaran. Sensor gas IoT dapat mendeteksi keberadaan asap sejak fase awal dan memberikan peringatan dini sebelum api membesar.</li><li><strong>Otomasi Tanggap Darurat (Evakuasi):</strong> IoT tidak hanya memantau, tetapi juga bertindak. Jika sensor mendeteksi kondisi bahaya (suhu ekstrem atau asap tebal), sistem secara otomatis dapat memerintahkan aktuator (seperti motor Servo) untuk membuka pintu secara permanen agar staf di dalam bisa segera berevakuasi tanpa terhalang pintu yang terkunci.</li><li><strong>Pemantauan Terpusat (Dashboard):</strong> Seluruh data dari berbagai lab atau ruang <em>server</em> dikirim melalui protokol jaringan (seperti MQTT) menuju satu <em>dashboard</em> antarmuka (misalnya menggunakan Node-RED), sehingga admin dapat memantau seluruh ekosistem gedung hanya dari satu layar monitor.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Ln0iXJtCxtyjNcG2qFC4Gw.png" /></figure><h3><strong>Keuntungan Implementasi IoT dalam Lab dan Ruang Server</strong></h3><ul><li><strong>Keamanan Aset Maksimal:</strong> Mencegah pencurian dan sabotase perangkat keras dengan membatasi akses secara ketat hanya kepada pihak yang berwenang.</li><li><strong>Pencegahan Kerusakan Hardware:</strong> Deteksi <em>overheat</em> yang cepat memungkinkan pengelola untuk segera menyalakan pendingin cadangan atau mematikan <em>server</em> sementara, sehingga menghindari kerusakan permanen akibat panas berlebih.</li><li><strong>Peningkatan Efisiensi Manajemen:</strong> Pengelola IT tidak perlu lagi melakukan patroli keliling secara manual untuk mengecek suhu AC atau status kunci pintu di setiap ruangan, karena semuanya terpantau 24/7 secara digital.</li><li><strong>Keselamatan Keseluruhan (Safety First):</strong> Penggabungan sistem keamanan (kunci pintar) dengan keselamatan (sensor asap) meminimalisir jatuhnya korban jiwa, karena sistem memprioritaskan terbukanya akses keluar saat terjadi bencana.</li></ul><h3>Skenario &amp; Logika Sistem</h3><p>Sistem yang akan kita buat bekerja dengan alur yang cerdas namun menggunakan komponen yang efisien (hanya 1 Servo):</p><ul><li><strong>Akses Utama Normal (RFID + Servo):</strong> Pintu ruangan terkunci secara <em>default</em> (0 derajat). Hanya <em>tap</em> kartu RFID terdaftar yang akan menggerakkan Servo Pintu untuk terbuka sementara (90 derajat) lalu mengunci kembali.</li><li><strong>Mode Evakuasi Darurat (DHT + Gas Sensor + Servo):</strong> Sensor akan terus memantau suhu dan kadar gas. Jika terdeteksi suhu ekstrem atau asap, sistem otomatis membunyikan peringatan di <em>dashboard</em> Node-RED dan <strong>memaksa Servo Pintu Utama terbuka secara permanen</strong> sebagai jalur evakuasi darurat hingga kondisi aman.</li></ul><h3>Hardware Time: Rangkaian Sensor &amp; ESP32</h3><p>Siapkan komponen tempur kalian dan hubungkan ke ESP32 sesuai <em>pinout</em> berikut. Hati-hati saat merangkai agar tidak terjadi korsleting!</p><ul><li><strong>Modul RFID RC522:</strong> SDA (GPIO 5), SCK (GPIO 18), MOSI (GPIO 23), MISO (GPIO 19), RST (GPIO 22), 3.3V ke 3.3V ESP32.</li><li><strong>Sensor Gas MQ-2:</strong> VCC (Vin/5V), GND (GND), A0 (Analog Out) -&gt; GPIO 34.</li><li><strong>Sensor DHT22:</strong> VCC (3.3V atau 5V), GND (GND), Data -&gt; GPIO 4.</li><li><strong>Servo (Pintu Utama):</strong> VCC (Vin/5V), GND (GND), Signal -&gt; GPIO 13.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*1b8MZx86DmhYIhvDQkurng.png" /><figcaption>Prototype</figcaption></figure><blockquote><strong><em>Note:</em></strong><em> Jika pergerakan Servo membuat ESP32 kalian </em>restart<em> (karena kurang arus), sangat disarankan menggunakan sumber daya 5V eksternal terpisah khusus untuk Servo.</em></blockquote><h3>Library Arduino IDE</h3><p>Pastikan kalian sudah menginstal <em>library</em> “sakti” berikut ini melalui <em>Library Manager</em> di Arduino IDE:</p><ol><li><strong>PubSubClient</strong> by Nick O’Leary (Untuk komunikasi MQTT)</li><li><strong>DHT sensor library</strong> by Adafruit</li><li><strong>MFRC522</strong> by GithubCommunity (Untuk sensor RFID)</li><li><strong>ESP32Servo</strong> by Kevin Harrington (Wajib pakai ini agar sinyal Servo stabil di ESP32)</li></ol><h3>The Code</h3><p><em>Copy-paste</em> kode di bawah ini. Jangan lupa ubah ssid, password, dan ganti UID_MASTER dengan kode UID dari kartu RFID kalian sendiri (bisa dicek di Serial Monitor saat kartu di-<em>tap</em> pertama kali).</p><pre>#include &lt;WiFi.h&gt;<br>#include &lt;PubSubClient.h&gt;<br>#include &lt;SPI.h&gt;<br>#include &lt;MFRC522.h&gt;<br>#include &lt;DHT.h&gt;<br>#include &lt;ESP32Servo.h&gt;<br><br>// Konfigurasi Pin<br>#define MQ2PIN 34<br>#define DHTPIN 4<br>#define DHTTYPE DHT22<br>#define SS_PIN 5<br>#define RST_PIN 22<br>#define SERVO_PINTU_PIN 13<br><br>// Konfigurasi WiFi &amp; MQTT<br>const char* ssid = &quot;Vivov27&quot;;<br>const char* password = &quot;1234567890&quot;;<br>const char* mqtt_server = &quot;broker.hivemq.com&quot;;<br><br>// Inisialisasi Objek<br>WiFiClient espClient;<br>PubSubClient client(espClient);<br>MFRC522 rfid(SS_PIN, RST_PIN);<br>DHT dht(DHTPIN, DHTTYPE);<br>Servo servoPintu;<br><br>// Daftar UID yang diizinkan akses<br>String UID_AKSES[] = {<br>  &quot;739419AB&quot;,<br>  &quot;63454A06&quot;,<br>  &quot;F3562010&quot;<br>};<br><br>const int JUMLAH_KARTU = 3;<br><br>unsigned long lastMsg = 0;<br>bool modeDarurat = false;<br><br>// Fungsi cek UID<br>bool cekAkses(String uid) {<br>  for (int i = 0; i &lt; JUMLAH_KARTU; i++) {<br>    if (uid == UID_AKSES[i]) {<br>      return true;<br>    }<br>  }<br>  return false;<br>}<br><br>void setup_wifi() {<br>  Serial.print(&quot;Connecting WiFi...&quot;);<br>  WiFi.begin(ssid, password);<br><br>  while (WiFi.status() != WL_CONNECTED) {<br>    delay(500);<br>    Serial.print(&quot;.&quot;);<br>  }<br><br>  Serial.println(&quot; Connected!&quot;);<br>}<br><br>void reconnect() {<br>  while (!client.connected()) {<br>    Serial.print(&quot;MQTT connecting...&quot;);<br><br>    String clientId = &quot;AMCC-Lab-&quot; + String(random(0xffff), HEX);<br><br>    if (client.connect(clientId.c_str())) {<br>      Serial.println(&quot;Connected!&quot;);<br>    } else {<br>      delay(3000);<br>    }<br>  }<br>}<br><br>void setup() {<br>  Serial.begin(115200);<br><br>  SPI.begin();<br>  rfid.PCD_Init();<br>  dht.begin();<br><br>  servoPintu.attach(SERVO_PINTU_PIN);<br><br>  // Posisi Default (Pintu Terkunci)<br>  servoPintu.write(0);<br><br>  setup_wifi();<br>  client.setServer(mqtt_server, 1883);<br>}<br><br>void loop() {<br><br>  if (!client.connected()) reconnect();<br>  client.loop();<br><br>  unsigned long now = millis();<br><br>  // 1. PENGIRIMAN DATA LINGKUNGAN &amp; CEK KEAMANAN (Setiap 2 Detik)<br>  if (now - lastMsg &gt; 2000) {<br><br>    lastMsg = now;<br><br>    float t = dht.readTemperature();<br>    int gasVal = analogRead(MQ2PIN);<br><br>    if (!isnan(t))<br>      client.publish(&quot;amcc/lab/suhu&quot;, String(t).c_str());<br><br>    client.publish(&quot;amcc/lab/gas&quot;, String(gasVal).c_str());<br><br>    Serial.printf(&quot;Suhu: %.2f C | Gas: %d \n&quot;, t, gasVal);<br><br>    // Logika Darurat Keamanan<br>    if (gasVal &gt; 1500 || t &gt; 35.0) {<br><br>      modeDarurat = true;<br><br>      servoPintu.write(90); // Paksa pintu terbuka untuk evakuasi<br><br>      client.publish(<br>        &quot;amcc/lab/status&quot;,<br>        &quot;BAHAYA: EVAKUASI! PINTU TERBUKA&quot;<br>      );<br><br>    } else {<br><br>      if (modeDarurat) {<br>        servoPintu.write(0); // Kunci kembali jika sudah aman<br>        modeDarurat = false;<br>      }<br><br>      client.publish(&quot;amcc/lab/status&quot;, &quot;AMAN&quot;);<br>    }<br>  }<br><br>  // 2. PEMBACAAN RFID UNTUK AKSES PINTU NORMAL<br>  // Hanya bisa baca RFID jika tidak dalam mode darurat<br>  if (!modeDarurat &amp;&amp;<br>      rfid.PICC_IsNewCardPresent() &amp;&amp;<br>      rfid.PICC_ReadCardSerial()) {<br><br>    String uid = &quot;&quot;;<br><br>    for (byte i = 0; i &lt; rfid.uid.size; i++) {<br>      uid += String(rfid.uid.uidByte[i] &lt; 0x10 ? &quot;0&quot; : &quot;&quot;);<br>      uid += String(rfid.uid.uidByte[i], HEX);<br>    }<br><br>    uid.toUpperCase();<br><br>    Serial.println(&quot;====================&quot;);<br>    Serial.println(&quot;RFID TERDETEKSI&quot;);<br>    Serial.println(&quot;UID : &quot; + uid);<br>    Serial.println(&quot;====================&quot;);<br><br>    if (cekAkses(uid)) {<br><br>      Serial.println(&quot;AKSES DITERIMA&quot;);<br><br>      client.publish(<br>        &quot;amcc/lab/rfid&quot;,<br>        &quot;AKSES DITERIMA&quot;<br>      );<br><br>      servoPintu.write(90); // Buka pintu<br>      delay(3000);          // Tahan 3 detik<br>      servoPintu.write(0);  // Kunci kembali<br><br>      client.publish(<br>        &quot;amcc/lab/rfid&quot;,<br>        &quot;PINTU TERKUNCI&quot;<br>      );<br><br>    } else {<br><br>      Serial.println(&quot;AKSES DITOLAK&quot;);<br><br>      client.publish(<br>        &quot;amcc/lab/rfid&quot;,<br>        &quot;AKSES DITOLAK!&quot;<br>      );<br>    }<br><br>    rfid.PICC_HaltA();<br>  }<br>}</pre><h3>Building the Dashboard</h3><p>Setelah <em>code</em> berhasil di-<em>upload</em> dan Serial Monitor menampilkan tulisan “Connected!”, saatnya meracik antarmuka <em>monitoring</em> kita di Node-RED!</p><ul><li>Buka browser dan akses <a href="http://localhost:1880.">http://localhost:1880.</a></li><li>Pastikan kalian sudah menginstal <em>palette</em> node-red-dashboard.</li><li>Klik menu titik tiga di pojok kanan atas, lalu pilih <strong>Import</strong>.</li><li><em>Copy-paste</em> kode JSON di bawah ini ke dalam kolom import, lalu klik <strong>Import</strong>.</li></ul><pre>[<br>    {<br>        &quot;id&quot;: &quot;tab_amcc_lab&quot;,<br>        &quot;type&quot;: &quot;ui_tab&quot;,<br>        &quot;name&quot;: &quot;Smart Lab Monitor&quot;,<br>        &quot;icon&quot;: &quot;dashboard&quot;,<br>        &quot;order&quot;: 1<br>    },<br>    {<br>        &quot;id&quot;: &quot;group_env&quot;,<br>        &quot;type&quot;: &quot;ui_group&quot;,<br>        &quot;name&quot;: &quot;Environment Status&quot;,<br>        &quot;tab&quot;: &quot;tab_amcc_lab&quot;,<br>        &quot;order&quot;: 1,<br>        &quot;disp&quot;: true,<br>        &quot;width&quot;: &quot;6&quot;<br>    },<br>    {<br>        &quot;id&quot;: &quot;group_sec&quot;,<br>        &quot;type&quot;: &quot;ui_group&quot;,<br>        &quot;name&quot;: &quot;Security &amp; Access&quot;,<br>        &quot;tab&quot;: &quot;tab_amcc_lab&quot;,<br>        &quot;order&quot;: 2,<br>        &quot;disp&quot;: true,<br>        &quot;width&quot;: &quot;6&quot;<br>    },<br>    {<br>        &quot;id&quot;: &quot;broker_hivemq&quot;,<br>        &quot;type&quot;: &quot;mqtt-broker&quot;,<br>        &quot;name&quot;: &quot;HiveMQ Public&quot;,<br>        &quot;broker&quot;: &quot;broker.hivemq.com&quot;,<br>        &quot;port&quot;: &quot;1883&quot;<br>    },<br>    {<br>        &quot;id&quot;: &quot;mqtt_in_suhu&quot;,<br>        &quot;type&quot;: &quot;mqtt in&quot;,<br>        &quot;name&quot;: &quot;Suhu Server&quot;,<br>        &quot;topic&quot;: &quot;amcc/lab/suhu&quot;,<br>        &quot;qos&quot;: &quot;0&quot;,<br>        &quot;datatype&quot;: &quot;auto-detect&quot;,<br>        &quot;broker&quot;: &quot;broker_hivemq&quot;,<br>        &quot;x&quot;: 110,<br>        &quot;y&quot;: 80,<br>        &quot;wires&quot;: [[&quot;gauge_suhu&quot;]]<br>    },<br>    {<br>        &quot;id&quot;: &quot;mqtt_in_gas&quot;,<br>        &quot;type&quot;: &quot;mqtt in&quot;,<br>        &quot;name&quot;: &quot;Kadar Gas&quot;,<br>        &quot;topic&quot;: &quot;amcc/lab/gas&quot;,<br>        &quot;qos&quot;: &quot;0&quot;,<br>        &quot;datatype&quot;: &quot;auto-detect&quot;,<br>        &quot;broker&quot;: &quot;broker_hivemq&quot;,<br>        &quot;x&quot;: 100,<br>        &quot;y&quot;: 140,<br>        &quot;wires&quot;: [[&quot;gauge_gas&quot;, &quot;chart_gas&quot;]]<br>    },<br>    {<br>        &quot;id&quot;: &quot;mqtt_in_rfid&quot;,<br>        &quot;type&quot;: &quot;mqtt in&quot;,<br>        &quot;name&quot;: &quot;Log Akses&quot;,<br>        &quot;topic&quot;: &quot;amcc/lab/rfid&quot;,<br>        &quot;qos&quot;: &quot;0&quot;,<br>        &quot;datatype&quot;: &quot;auto-detect&quot;,<br>        &quot;broker&quot;: &quot;broker_hivemq&quot;,<br>        &quot;x&quot;: 100,<br>        &quot;y&quot;: 240,<br>        &quot;wires&quot;: [[&quot;text_rfid&quot;]]<br>    },<br>    {<br>        &quot;id&quot;: &quot;mqtt_in_status&quot;,<br>        &quot;type&quot;: &quot;mqtt in&quot;,<br>        &quot;name&quot;: &quot;Status Lab&quot;,<br>        &quot;topic&quot;: &quot;amcc/lab/status&quot;,<br>        &quot;qos&quot;: &quot;0&quot;,<br>        &quot;datatype&quot;: &quot;auto-detect&quot;,<br>        &quot;broker&quot;: &quot;broker_hivemq&quot;,<br>        &quot;x&quot;: 100,<br>        &quot;y&quot;: 300,<br>        &quot;wires&quot;: [[&quot;text_status&quot;]]<br>    },<br>    {<br>        &quot;id&quot;: &quot;gauge_suhu&quot;,<br>        &quot;type&quot;: &quot;ui_gauge&quot;,<br>        &quot;group&quot;: &quot;group_env&quot;,<br>        &quot;order&quot;: 1,<br>        &quot;title&quot;: &quot;Suhu Ruangan&quot;,<br>        &quot;label&quot;: &quot;°C&quot;,<br>        &quot;format&quot;: &quot;{{value}}&quot;,<br>        &quot;min&quot;: 0,<br>        &quot;max&quot;: &quot;50&quot;,<br>        &quot;colors&quot;: [&quot;#00b500&quot;, &quot;#e6e600&quot;, &quot;#ca3838&quot;],<br>        &quot;x&quot;: 320,<br>        &quot;y&quot;: 80,<br>        &quot;wires&quot;: []<br>    },<br>    {<br>        &quot;id&quot;: &quot;gauge_gas&quot;,<br>        &quot;type&quot;: &quot;ui_gauge&quot;,<br>        &quot;group&quot;: &quot;group_env&quot;,<br>        &quot;order&quot;: 2,<br>        &quot;title&quot;: &quot;Level Gas/Asap&quot;,<br>        &quot;label&quot;: &quot;RAW&quot;,<br>        &quot;format&quot;: &quot;{{value}}&quot;,<br>        &quot;min&quot;: 0,<br>        &quot;max&quot;: &quot;4095&quot;,<br>        &quot;colors&quot;: [&quot;#00b500&quot;, &quot;#e6e600&quot;, &quot;#ca3838&quot;],<br>        &quot;x&quot;: 320,<br>        &quot;y&quot;: 140,<br>        &quot;wires&quot;: []<br>    },<br>    {<br>        &quot;id&quot;: &quot;chart_gas&quot;,<br>        &quot;type&quot;: &quot;ui_chart&quot;,<br>        &quot;group&quot;: &quot;group_env&quot;,<br>        &quot;order&quot;: 3,<br>        &quot;title&quot;: &quot;Grafik Kebocoran&quot;,<br>        &quot;label&quot;: &quot;Gas&quot;,<br>        &quot;chartType&quot;: &quot;line&quot;,<br>        &quot;x&quot;: 330,<br>        &quot;y&quot;: 180,<br>        &quot;wires&quot;: [[]]<br>    },<br>    {<br>        &quot;id&quot;: &quot;text_rfid&quot;,<br>        &quot;type&quot;: &quot;ui_text&quot;,<br>        &quot;group&quot;: &quot;group_sec&quot;,<br>        &quot;order&quot;: 1,<br>        &quot;title&quot;: &quot;Akses Terakhir:&quot;,<br>        &quot;format&quot;: &quot;{{msg.payload}}&quot;,<br>        &quot;layout&quot;: &quot;row-spread&quot;,<br>        &quot;x&quot;: 330,<br>        &quot;y&quot;: 240,<br>        &quot;wires&quot;: []<br>    },<br>    {<br>        &quot;id&quot;: &quot;text_status&quot;,<br>        &quot;type&quot;: &quot;ui_text&quot;,<br>        &quot;group&quot;: &quot;group_sec&quot;,<br>        &quot;order&quot;: 2,<br>        &quot;title&quot;: &quot;Status Keamanan:&quot;,<br>        &quot;format&quot;: &quot;{{msg.payload}}&quot;,<br>        &quot;layout&quot;: &quot;row-spread&quot;,<br>        &quot;x&quot;: 330,<br>        &quot;y&quot;: 300,<br>        &quot;wires&quot;: []<br>    }<br>]</pre><ul><li>Klik tombol <strong>Deploy</strong> berwarna merah di pojok kanan atas.</li><li>Buka <em>dashboard</em> kalian di http://localhost:1880/ui. Coba <em>tap</em> kartu RFID untuk melihat log akses, lalu dekatkan korek api gas ke sensor MQ-2 untuk melihat pintu otomatis terbuka saat mode darurat menyala!</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*BBksdxvFOFiKDuWpvP5VhA.png" /><figcaption>Dashboard node-red</figcaption></figure><h3>Penutup</h3><p>Gimana IoT Gengs? Lewat satu mikrokontroler ESP32 dan satu aktuator Servo, kita sudah berhasil membuat sistem cerdas yang tidak hanya mengamankan ruangan, tapi juga menyelamatkan nyawa saat terjadi bahaya kebakaran atau kebocoran gas. Efisien banget, kan?</p><p>Kalau ada yang <em>error</em> saat ngoding, jangan panik. <em>Debug is part of the fun!</em> Langsung aja kita diskusikan di kelas.</p><p>Sampai jumpa………</p><p><strong>AMCC</strong></p><p>Learning By Doing</p><p>Learning By Teaching</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=369f84333978" width="1" height="1" alt=""><hr><p><a href="https://medium.com/amcc-amikom/smart-lab-server-room-protection-system-with-node-red-369f84333978">Smart Lab &amp; Server Room Protection System with Node-RED</a> was originally published in <a href="https://medium.com/amcc-amikom">Amikom Computer Club</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Flutter #5 : Firebase Authentication]]></title>
            <link>https://medium.com/amcc-amikom/flutter-5-firebase-authentication-a4e6c724e420?source=rss----fe834c257357---4</link>
            <guid isPermaLink="false">https://medium.com/p/a4e6c724e420</guid>
            <category><![CDATA[mobile-development]]></category>
            <category><![CDATA[mobile-programming]]></category>
            <dc:creator><![CDATA[Aditya Pratama]]></dc:creator>
            <pubDate>Thu, 11 Jun 2026 15:45:51 GMT</pubDate>
            <atom:updated>2026-06-11T15:45:50.251Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*yZAZNjTR4J-xEFD_QX4sPw.png" /></figure><p><strong>Halo Sobat AMCC</strong> 😍<br>Gimana nih kabarnya, semoga tetap sehat ya 😁<br>Kita balik lagi ke pelatihan kita nih. Pada pelatihan sebelumnya kita sudah belajar membuat <strong>Halaman Profil</strong> yang bagus nih. Setelah ini, kita akan lanjut ke materi yang tentunya lebih seru lagi dong 👍</p><p>Pada pelatihan kali ini, kita akan belajar menerapkan <strong>Firebase</strong> pada aplikasi yang sudah dibuat. Pasti teman-teman bertanya, <strong>Apa itu Firebase ? Fitur-fitur apa aja sih yang ada di dalamnya ?</strong></p><h4><strong>Daftar isi</strong></h4><p>· <a href="#623a"><strong>Apa itu Firebase ?</strong></a><br> ∘ <a href="#c804">Keunggulan Firebase</a><br>·<strong> </strong><a href="#88fa"><strong>Fitur dalam Firebase</strong></a><br> ∘ <a href="#d39b">A. Firebase Authentication</a><br> ∘ <a href="#2862">B. Cloud Firestore</a><br> ∘ <a href="#4286">C. Firebase Storage</a><br>· <a href="#5e47"><strong>Persiapan Project</strong></a><strong><br>· </strong><a href="#d012"><strong>Membuat Project di Firebase</strong></a><strong><br>· </strong><a href="#2b9a"><strong>Install Firebase CLI dan FlutterFire CLI</strong></a><strong><br>· </strong><a href="#ed93"><strong>Install Dependencies (Library yang dibutuhkan)</strong></a><strong><br>· </strong><a href="#c419"><strong>Inisialisasi Firebase / Langkah Awal Konfigurasi Firebase</strong></a><strong><br>· </strong><a href="#bf61"><strong>Membuat Authentication Services</strong></a><strong><br>· </strong><a href="#d1ee"><strong>Menerapkan Authentication Service pada Halaman Register dan Login</strong></a><br> ∘ <a href="#4d25">Halaman Register</a><br> ∘ <a href="#b5ac">Halaman Login</a><br>· <a href="#5248"><strong>Menampilkan data akun di Profile Page</strong></a><strong><br>· </strong><a href="#d737"><strong>Konfigurasi pada Firebase</strong></a><br> ∘ <a href="#85cb">Authentication</a><br> ∘ <a href="#c1b3">Firestore Rules</a><br> ∘ <a href="#e1a4">Membuat data untuk user (Optional tapi disarankan untuk awal)</a><br>· <a href="#352d"><strong>Hasil Proyek</strong></a><strong><br>· </strong><a href="#ae41"><strong>Materi Bonus : Kondisi Foto Profil</strong></a></p><p>Sebelum lanjut ke penarapannya kita akan membedah terlebih dahulu tentang Firebase.</p><h3><strong>Apa itu Firebase ?</strong></h3><p>Firebase adalah suatu platform <strong>Backend-as-a-Service (BaaS)</strong> yang dikembangkan oleh <strong>Google</strong> untuk <strong>membantu developer</strong> membangun suatu aplikasi <strong>website, Android, IOS dan Flutter</strong> tanpa harus membuat backend dari nol.</p><p>Simplenya, Firebase adalah sebuah layanan yang menyediakan berbagai fitur antara lain <strong>login pengguna, penyimpanan data dan file, notifikasi dan analisis aplikasi</strong>.</p><h4><strong>Keunggulan Firebase</strong></h4><ul><li>Tidak perlu membuat server backend sendiri</li><li>Mudah diintegrasikan dengan Flutter, Android, iOS, dan Web.</li><li>Data tersimpan di cloud.</li><li>Mendukung sinkronisasi data secara real-time.</li><li>Scalable untuk aplikasi kecil hingga besar.</li></ul><h3><strong>Fitur dalam Firebase</strong></h3><p>Berikut adalah beberapa fitur firebase yang akan kita gunakan untuk mengembangkan aplikasi :</p><h4><strong>A. Firebase Authentication</strong></h4><p>Firebase Authentication sesuai dengan namanya digunakan untuk <strong>authentication (login pengguna)</strong>. Fiturnya terdiri dari <strong>Login Email &amp; Password, Google, Facebook, Github, Apple, dan Nomor Telepon</strong>.</p><p>Contoh penggunaannya</p><ul><li>Register</li><li>Login</li><li>Logout</li><li>Reset Password</li></ul><h4><strong>B. Cloud Firestore</strong></h4><p>Cloud Firestore adalah tempat untuk <strong>menyimpan data</strong> pada aplikasi kita. Kelebihan dari Cloud Firestore adalah <strong>Real Time, Cepat, Mendukung query kompleks, Offline persistence</strong>.</p><h4><strong>C. Firebase Storage</strong></h4><p>Kalau Cloud Firestore adalah tempat untuk menyimpan data pada aplikasi kita, maka Firebase Storage akan membantunya dalam menyimpan file. Contoh file yang biasanya disimpan adalah Foto Profil, Dokumen PDF, Video dan Audio.</p><p>Okay teman-teman, gimana nih setelah mempelajari materi terkait Firebase.</p><p><strong>Apakah bingung atau semakin penasaran nih ? 🤔🤔🤔<br></strong>Selanjutnya, kita akan langsung saja lanjut ke <strong>sesi Praktikum</strong>.</p><p>Sebelum melakukan sesi praktikum, ada beberapa hal yang harus teman-teman persiapkan yaitu :</p><ul><li><strong>Siap untuk belajar hal baru</strong></li><li><strong>Persiapan waktu luang</strong></li><li><strong>Persiapan project sebelumnya apabila belum selesai</strong></li></ul><h3><strong>Persiapan Project</strong></h3><p>Bagi teman-teman yang berhalangan hadir atau proyek pelatihan kemarin belum selesai, bisa menyalin proyek pertemuan sebelumnya.</p><pre>git clone -b pertemuan-8 https://github.com/amccamikom/amcc-mobile-2025.git pertemuan-9</pre><h3><strong>Membuat Project di Firebase</strong></h3><p>Dikarenakan kita akan menggunakan firebase dalam login user, penyimpanan data dan foto, maka kita harus membuat project baru. Langkah-langkah untuk membuat projectnya, yaitu :</p><ul><li>Buka browser kesukaan teman-teman, lalu cari<strong> “Firebase”</strong>.</li><li>Teman-teman bisa klik website yang paling atas yaitu <a href="http://firebase.google.com/">firebase.google.com</a></li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/625/1*tzZTQUa93I-g_v-D6P1L7Q.png" /></figure><ul><li>Pada bagian pojok atas, terdapat tombol bertuliskan <strong>“Buka Konsol”</strong> atau <strong>“Open Console”</strong> itu di klik saja.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/273/1*3s9-MTkDPiDVWcliV3Gang.png" /></figure><ul><li>Pada tampilan ini, teman-teman bisa klik <strong>“Create a new Firebase project”</strong> untuk membuat project baru.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*eLR-BVkbI7t1EbKxvm84-w.png" /></figure><ul><li>Berikan nama project yang teman-teman inginkan, pada tampilan diatas aku memberikan nama <strong>“edutrack”</strong> sesuai dengan aplikasi kita.</li><li>Jika sudah jadi, tampilannya akan seperti ini.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*_sBC6PwxPQLDP_3cRZsHQA.png" /></figure><h3><strong>Install Firebase CLI dan FlutterFire CLI</strong></h3><p>Sebelum menambahkan firebase di aplikasi kita, terdapat beberapa langkah yang harus dilakukan yaitu :</p><ul><li><strong>Install Firebase CLI dan FlutterFire CLI</strong></li></ul><pre>npm install -g firebase-tools</pre><p>NB : Pastikan node js sudah terinstall di komputer/laptop teman-teman. Apabila <strong>belum menginstal nodeJS</strong> bisa download melalui link ini : <a href="https://nodejs.org/en/download">https://nodejs.org/en/download</a></p><ul><li><strong>Login Firebase</strong></li></ul><pre>firebase login</pre><ul><li><strong>Install FlutterFire CLI</strong></li></ul><pre>dart pub global activate flutterfire_cli</pre><ul><li><strong>Konfigurasi Firebase ke Flutter</strong></li></ul><pre>flutterfire configure</pre><p><strong>Fun Fact 🤔<br></strong>Kode <strong>flutterfire configure</strong> akan menghasilkan file <strong>firebase_options.dart</strong> di folder<strong> lib</strong></p><h3><strong>Install Dependencies (Library yang dibutuhkan)</strong></h3><p>Kita perlu menginstall beberapa library yang dibutuhkan seperti <strong>firebase_core, firebase_auth, cloud_firestore</strong></p><ul><li><strong>Firebase Core</strong></li></ul><pre>flutter pub add firebase_core </pre><ul><li><strong>Firebase Auth</strong></li></ul><pre>flutter pub add firebase_auth</pre><ul><li><strong>Cloud Firestore</strong></li></ul><pre>flutter pub add cloud_firestore</pre><p>Atau bisa <strong>diinstall sekaligus</strong> dengan kode di bawah ini</p><pre>flutter pub add firebase_core firebase_auth cloud_firestore</pre><h3><strong>Inisialisasi Firebase / Langkah Awal Konfigurasi Firebase</strong></h3><p>Sebelum kita menerapkan lebih jauh firebase di project kita, terdapat konfigurasi awal yang harus dilakukan di halaman utama project kita.</p><p>Teman-teman bisa buka file <strong>lib/main.dart. </strong>Berikut adalah kode yang perlu ditambahkan.</p><ul><li>Menambahkan library <strong>firebase_core dan firebase_options</strong></li></ul><pre>import &#39;package:firebase_core/firebase_core.dart&#39;;<br>import &#39;firebase_options.dart&#39;;</pre><ul><li>Mengubah fungsi main menjadi <strong>asynchronous</strong> untuk mendukung <strong>penggunaan firebase</strong></li></ul><pre>void main() async {<br>  WidgetsFlutterBinding.ensureInitialized();<br>  <br>  await Firebase.initializeApp(<br>  options: DefaultFirebaseOptions.currentPlatform,<br>  );<br>  <br>  runApp(const MyApp());<br>}</pre><h3><strong>Membuat Authentication Services</strong></h3><p>Selanjutnya, kita akan membuat <strong>Authentication Service</strong>. Sebelum lanjut, aku mau jelasin secara singkat apa itu service ?</p><p><strong>Service secara singkat adalah kode program yang melatar belakangi suatu aksi.</strong></p><p>Contoh penerapannya adalah pada saat kita login akun. Nah, kita tidak tahu proses apa yang dilakukan di belakangnya karena dihandle oleh service. Tiba-tiba sudah masuk ke halaman selanjutnya.</p><p>Lanjut, kita akan membuat <strong>Authentication Service</strong>. Langkah-langkahnya adalah</p><ul><li>Buatlah folder dengan nama <strong>services</strong> di dalam lib</li><li>Buat file dengan nama <strong>auth_service.dart</strong></li><li>Nanti hasilnya akan seperti ini</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/238/1*2i7yweeHFyozCCMaBd-0pg.png" /></figure><ul><li>Kemudian, kita akan mengisi file program <strong>auth_service</strong></li></ul><pre>import &#39;package:cloud_firestore/cloud_firestore.dart&#39;;<br>import &#39;package:firebase_auth/firebase_auth.dart&#39;;<br><br>class AuthService {<br>  final FirebaseAuth _auth = FirebaseAuth.instance;<br>  final FirebaseFirestore _firestore = FirebaseFirestore.instance;<br><br>  Future&lt;void&gt; register({<br>    required String name,<br>    required String email,<br>    required String password,<br>    required String gender,<br>  }) async {<br>    UserCredential userCredential =<br>        await _auth.createUserWithEmailAndPassword(<br>      email: email,<br>      password: password,<br>    );<br><br>    final uid = userCredential.user!.uid;<br><br>    await _firestore.collection(&#39;users&#39;).doc(uid).set({<br>      &#39;uid&#39;: uid,<br>      &#39;name&#39;: name,<br>      &#39;email&#39;: email,<br>      &#39;gender&#39;: gender,<br>      &#39;role&#39;: &#39;Mahasiswa&#39;,<br>      &#39;createdAt&#39;: FieldValue.serverTimestamp(),<br>    });<br>  }<br><br>  Future&lt;void&gt; login({<br>    required String email,<br>    required String password,<br>  }) async {<br>    await _auth.signInWithEmailAndPassword(<br>      email: email,<br>      password: password,<br>    );<br>  }<br><br>  Future&lt;void&gt; logout() async {<br>    await _auth.signOut();<br>  }<br><br>  Future&lt;Map&lt;String, dynamic&gt;?&gt; getUserData() async {<br>    final uid = _auth.currentUser!.uid;<br><br>    final doc = await _firestore.collection(&#39;users&#39;).doc(uid).get();<br><br>    return doc.data();<br>  }<br>}</pre><h3><strong>Menerapkan Authentication Service pada Halaman Register dan Login</strong></h3><h4><strong>Halaman Register</strong></h4><p>Pada halaman register, terdapat beberapa perubahan yang perlu kita lakukan yaitu :</p><ul><li><strong>Menambahkan controller untuk name dan gender</strong></li></ul><pre>final TextEditingController _nameController = TextEditingController(); // name<br>final TextEditingController _emailController = TextEditingController();<br>final TextEditingController _passwordController = TextEditingController();<br><br>String? selectedGender; // gender</pre><ul><li><strong>Membuat fungsi register dengan penggunaan AuthService</strong></li></ul><pre>  final AuthService authService = AuthService(); // Membuat objek authService<br>  bool isLoading = false; // Loading status<br><br>  Future&lt;void&gt;register() async {<br>    // Melakukan deklarasi awal bahwa loading bernilai benar<br>    setState(() {<br>      isLoading = true;<br>    });<br><br>    try {<br>      // Melakukan akses ke fungsi register di authService<br>      await authService.register(<br>        name: _nameController.text.trim(),<br>        email: _emailController.text.trim(),<br>        password: _passwordController.text.trim(),<br>        gender: selectedGender!<br>      );<br>      <br>      // Jika tidak ada error maka lanjut ke tampilan login.<br>      if(!mounted) return;<br>      <br>      Navigator.pushReplacementNamed(context, &#39;/login&#39;);<br>    } catch(e) {<br>      String message;<br>      <br>      // Pesan error yang ditampilkan<br>      switch (e) {<br>        case &#39;email-already-in-use&#39;:<br>          message = &quot;Email sudah terdaftar.&quot;;<br>          break;<br><br>        case &#39;weak-password&#39;:<br>          message = &quot;Password minimal 6 karakter.&quot;;<br>          break;<br><br>        case &#39;invalid-email&#39;:<br>          message = &quot;Format email tidak valid.&quot;;<br>          break;<br><br>        default:<br>          message = &quot;Registrasi gagal. Silakan coba kembali.&quot;;<br>      }<br>      <br>      // Menampilkan dialog apabila terjadi kesalahan pada registrasi akun<br>      showDialog(<br>        context: context,<br>        builder: (_) =&gt; AlertDialog(<br>          title: Text(<br>            &quot;Registrasi Gagal&quot;,<br>            style: GoogleFonts.poppins(<br>              fontWeight: FontWeight.w600,<br>            ),<br>          ),<br>          content: Text(<br>            message,<br>            style: GoogleFonts.poppins(<br>              fontWeight: FontWeight.w400,<br>            ),<br>          ),<br>        ),<br>      );<br>    }<br>    <br>    // Memberikan status bahwa loading sudah selesai<br>    setState(() {<br>      isLoading = false;<br>    });<br>  }</pre><ul><li><strong>Menambahkan tampilan untuk input Nama Lengkap di halaman register</strong></li></ul><pre>buildTextField(label: &#39;Nama Lengkap&#39;, hintText: &#39;Masukkan nama lengkap&#39;, controller: _nameController),<br>const SizedBox(height: 20),</pre><ul><li><strong>Menambahkan tampilan untuk input Jenis Kelamin</strong></li></ul><pre>import &#39;package:flutter/material.dart&#39;;<br>import &#39;package:google_fonts/google_fonts.dart&#39;;<br><br>class GenderOptions extends StatelessWidget {<br>  final String? selectedGender;<br>  final ValueChanged&lt;String?&gt; onChanged;<br><br>  const GenderOptions({<br>    super.key,<br>    required this.selectedGender,<br>    required this.onChanged,<br>  });<br><br>  @override<br>  Widget build(BuildContext context) {<br>    return Column(<br>      crossAxisAlignment: CrossAxisAlignment.start,<br>      children: [<br>        Text(<br>          &quot;Jenis Kelamin&quot;,<br>          style: GoogleFonts.poppins(<br>            fontWeight: FontWeight.bold,<br>            fontSize: 14,<br>          ),<br>        ),<br>        const SizedBox(height: 8),<br><br>        DropdownButtonFormField&lt;String&gt;(<br>          initialValue: selectedGender,<br>          decoration: InputDecoration(<br>            hintText: &quot;Pilih jenis kelamin&quot;,<br>            contentPadding: const EdgeInsets.symmetric(<br>              horizontal: 16,<br>              vertical: 14,<br>            ),<br>            border: OutlineInputBorder(<br>              borderRadius: BorderRadius.circular(12),<br>            ),<br>            enabledBorder: OutlineInputBorder(<br>              borderRadius: BorderRadius.circular(12),<br>              borderSide: const BorderSide(<br>                color: Color(0xFFE0E0E0),<br>              ),<br>            ),<br>            focusedBorder: OutlineInputBorder(<br>              borderRadius: BorderRadius.circular(12),<br>              borderSide: const BorderSide(<br>                color: Colors.blue,<br>                width: 2,<br>              ),<br>            ),<br>          ),<br>          items: const [<br>            DropdownMenuItem(<br>              value: &#39;Laki-laki&#39;,<br>              child: Text(&#39;Laki-laki&#39;),<br>            ),<br>            DropdownMenuItem(<br>              value: &#39;Perempuan&#39;,<br>              child: Text(&#39;Perempuan&#39;),<br>            ),<br>          ],<br>          onChanged: onChanged,<br>          validator: (value) {<br>            if (value == null || value.isEmpty) {<br>              return &#39;Jenis kelamin wajib dipilih&#39;;<br>            }<br>            return null;<br>          },<br>        ),<br>      ],<br>    );<br>  }<br>}</pre><ul><li><strong>Cara menggunakannya pada halaman register</strong></li></ul><pre>const SizedBox(height: 20),<br>GenderOptions(<br>  selectedGender: selectedGender,<br>  onChanged: (value) {<br>    setState(() {<br>      selectedGender = value;<br>    });<br>  },<br>),</pre><ul><li><strong>Membuat tombol Daftar berfungsi dengan register yang sudah dibuat</strong></li></ul><pre>SizedBox(<br>  width: double.infinity,<br>  child: ElevatedButton(<br>    onPressed: isLoading ? null : register,<br>    style: ElevatedButton.styleFrom(<br>      backgroundColor: const Color(0xFF3254FD),<br>      padding: const EdgeInsets.symmetric(vertical: 16),<br>      shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),<br>    ),<br>    child: Text(isLoading ? &#39;Loading...&#39; : &#39;Daftar&#39;, style:GoogleFonts.poppins(fontSize: 18, fontWeight: FontWeight.w600, color: Colors.white)),<br>  ),<br>),</pre><h4><strong>Halaman Login</strong></h4><p>Pada halaman login, kita akan melakukan perubahan untuk dihubungkan dengan firebase.</p><ul><li><strong>Hapus kode di bawah ini karena sudah tidak digunakan</strong></li></ul><pre>static List&lt;Map&lt;String, String&gt;&gt; userList = []; <br><br>static void addUser(String email, String password) {<br>  userList.add({<br>    &#39;email&#39;: email,<br>    &#39;password&#39;: password,<br>  });<br>}<br><br>static bool isUserValid(String email, String password) {<br>  return userList.any((user) =&gt; <br>    user[&#39;email&#39;] == email &amp;&amp; user[&#39;password&#39;] == password<br>  );<br>}</pre><ul><li><strong>Membuat fungsi login dengan penggunaan AuthService</strong></li></ul><pre>  final AuthService authService = AuthService(); // Membuat objek authService<br>  bool isLoading = false; // Loading status<br><br>    Future&lt;void&gt;login() async {<br>    setState(() {<br>      isLoading = true;<br>    });<br><br>    try {<br>      // Melakukan akses ke fungsi login di authService<br>      await authService.login(<br>        email: _emailController.text.trim(),<br>        password: _passwordController.text.trim(),<br>      );<br><br>      // Jika tidak ada error maka lanjut ke tampilan home.<br>      if(!mounted) return;<br><br>      Navigator.pushReplacementNamed(context, &#39;/home&#39;);<br>    } catch(e) {<br>      String message;<br><br>      // Pesan error yang ditampilkan<br>      switch (e) {<br>        case &#39;invalid-credential&#39;:<br>          message =<br>              &quot;Email atau password yang Anda masukkan salah. Silakan coba lagi.&quot;;<br>          break;<br><br>        case &#39;network-request-failed&#39;:<br>          message =<br>              &quot;Tidak dapat terhubung ke internet. Periksa koneksi Anda.&quot;;<br>          break;<br><br>        case &#39;too-many-requests&#39;:<br>          message =<br>              &quot;Terlalu banyak percobaan login. Silakan coba beberapa saat lagi.&quot;;<br>          break;<br><br>        default:<br>          message =<br>              &quot;Terjadi kesalahan saat proses login. Silakan coba kembali.&quot;;<br>      }<br>      <br>      // Menampilkan dialog apabila terjadi kesalahan pada login akun<br>      showDialog(<br>        context: context,<br>        builder: (context) =&gt; AlertDialog(<br>          title: Text(<br>            &quot;Login Gagal&quot;,<br>            style: GoogleFonts.poppins(<br>              fontWeight: FontWeight.w600,<br>            ),<br>          ),<br>          content: Text(<br>            message,<br>            style: GoogleFonts.poppins(<br>              fontWeight: FontWeight.w400,<br>            ),<br>          ),<br>          actions: [<br>            TextButton(<br>              onPressed: () =&gt; Navigator.pop(context),<br>              child: Text(<br>                &quot;OK&quot;,<br>                style: GoogleFonts.poppins(),<br>              ),<br>            ),<br>          ],<br>        ),<br>      );<br>    }<br>    <br>    // Memberikan status bahwa loading sudah selesai<br>    setState(() {<br>      isLoading = false;<br>    });<br>  }</pre><ul><li><strong>Membuat tombol Masuk berfungsi dengan login yang sudah dibuat</strong></li></ul><pre>SizedBox(<br>  width: double.infinity,<br>  child: ElevatedButton(<br>    onPressed: isLoading ? null : login,<br>    style: ElevatedButton.styleFrom(<br>      backgroundColor: const Color(0xFF3254FD),<br>      padding: const EdgeInsets.symmetric(vertical: 16),<br>      shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),<br>    ),<br>    child: Text(isLoading ? &#39;Loading...&#39; : &#39;Masuk&#39;, style:GoogleFonts.poppins(fontSize: 18, fontWeight: FontWeight.w600, color: Colors.white)),<br>  ),<br>),</pre><h3><strong>Menampilkan data akun di Profile Page</strong></h3><p>Setelah kita berhasil untuk menerapkan firebase di halaman Login dan Register, langkah selanjutnya yang perlu dilakukan adalah <strong>menampilkan data di Profile Page</strong>. Data yang akan ditampilkan adalah <strong>Nama Lengkap, Role / Status, Email, Hobi, No. Handphone</strong>.</p><ul><li><strong>Mengubah halaman Profil yang sebelumnya Stateless Widget menjadi Statefull Widget</strong></li></ul><pre>import &#39;package:flutter/material.dart&#39;;<br><br>class ProfilePage extends StatefulWidget {<br>  const ProfilePage({super.key});<br><br>  @override<br>  State&lt;ProfilePage&gt; createState() =&gt; _ProfilePageState();<br>}<br><br>class _ProfilePageState extends State&lt;ProfilePage&gt; {<br>  @override<br>  Widget build(BuildContext context) {<br>    return const Placeholder();<br>  }<br>}</pre><ul><li><strong>Membuat fungsi untuk load data dan logout dengan menggunakan authService</strong></li></ul><pre>  final AuthService authService = AuthService();<br><br>  late Future&lt;Map&lt;String, dynamic&gt;?&gt; userFuture;<br><br>  @override<br>  void initState() {<br>    super.initState();<br>    userFuture = authService.getUserData();<br>  }<br><br>  Future&lt;void&gt; logout() async {<br>    await authService.logout();<br><br>    if (!mounted) return;<br><br>    Navigator.of(context, rootNavigator: true).pushNamedAndRemoveUntil(<br>      &#39;/login&#39;,<br>      (route) =&gt; false,<br>    );<br>  }</pre><ul><li><strong>Membuat tampilan yang dinamis (karena mengambil data dari Firebase)</strong></li></ul><pre>body: FutureBuilder&lt;Map&lt;String, dynamic&gt;?&gt;(<br>        future: userFuture,<br>        builder: (context, snapshot) {<br>          if (snapshot.hasError) {<br>            return const Center(child: Text(&#39;Gagal memuat data profil&#39;));<br>          }<br><br>          if (!snapshot.hasData || snapshot.data == null) {<br>            return const Center(child: Text(&#39;Data profil tidak ditemukan&#39;));<br>          }<br><br>          final data = snapshot.data!;<br><br>          final name = data[&#39;name&#39;] ?? &#39;-&#39;;<br>          final email = data[&#39;email&#39;] ?? &#39;-&#39;;<br>          final hobby = data[&#39;hobby&#39;] ?? &#39;-&#39;;<br>          final gender = data[&#39;gender&#39;] ?? &#39;Laki-Laki&#39;;<br>          final phone = data[&#39;phone&#39;] ?? &#39;-&#39;;<br>          final role = data[&#39;role&#39;] ?? &#39;Mahasiswa&#39;;<br><br>          final infoItems = [<br>            (<br>              Icons.mail_outline,<br>              &#39;Email&#39;,<br>              email,<br>              email != &#39;-&#39; ? &#39;mailto:$email&#39; : null,<br>            ),<br>            (<br>              Icons.favorite_border,<br>              &#39;Hobi&#39;,<br>              hobby,<br>              null,<br>            ),<br>            (<br>              Icons.phone_outlined,<br>              &#39;No Handphone&#39;,<br>              phone,<br>              phone != &#39;-&#39; ? &#39;tel:$phone&#39; : null,<br>            ),<br>          ];<br><br>          return SingleChildScrollView(<br>            padding: const EdgeInsets.symmetric(horizontal: 24),<br>            child: Column(<br>              children: [<br>                const SizedBox(height: 20),<br><br>                CircleAvatar(<br>                  radius: 65,<br>                  backgroundImage: AssetImage(<br>                    gender == &#39;Perempuan&#39;<br>                      ? &#39;assets/female_profile.jpg&#39;<br>                      : &#39;assets/male_profile.jpg&#39;,<br>                  ),<br>                  backgroundColor: const Color(0xFFEFEFEF),<br>                ),<br><br>                const SizedBox(height: 20),<br><br>                Text(<br>                  name,<br>                  style: const TextStyle(<br>                    fontSize: 20,<br>                    fontWeight: FontWeight.bold,<br>                  ),<br>                ),<br><br>                const SizedBox(height: 8),<br><br>                Text(<br>                  role,<br>                  style: const TextStyle(<br>                    fontSize: 16,<br>                    color: Colors.black87,<br>                  ),<br>                ),<br><br>                const SizedBox(height: 40),<br><br>                ...infoItems.map(<br>                  (item) =&gt; _InfoCard(<br>                    icon: item.$1,<br>                    title: item.$2,<br>                    value: item.$3,<br>                    uriStr: item.$4,<br>                  ),<br>                ),<br><br>                const SizedBox(height: 20),<br><br>                SizedBox(<br>                  width: double.infinity,<br>                  height: 52,<br>                  child: ElevatedButton.icon(<br>                    onPressed: logout,<br>                    icon: const Icon(Icons.logout),<br>                    label: const Text(<br>                      &#39;Logout&#39;,<br>                      style: TextStyle(<br>                        fontSize: 16,<br>                        fontWeight: FontWeight.bold,<br>                      ),<br>                    ),<br>                    style: ElevatedButton.styleFrom(<br>                      backgroundColor: Colors.red,<br>                      foregroundColor: Colors.white,<br>                      shape: RoundedRectangleBorder(<br>                        borderRadius: BorderRadius.circular(12),<br>                      ),<br>                    ),<br>                  ),<br>                ),<br>              ],<br>            ),<br>          );<br>        },<br>      ),</pre><p><strong>Nanti hasil akhirnya akan seperti ini</strong></p><pre>import &#39;package:edutrack_app/services/auth_service.dart&#39;;<br>import &#39;package:flutter/material.dart&#39;;<br>import &#39;package:url_launcher/url_launcher.dart&#39;;<br><br>class ProfilePage extends StatefulWidget {<br>  const ProfilePage({super.key});<br><br>  @override<br>  State&lt;ProfilePage&gt; createState() =&gt; _ProfilePageState();<br>}<br><br>class _ProfilePageState extends State&lt;ProfilePage&gt; {<br>  final AuthService authService = AuthService();<br><br>  late Future&lt;Map&lt;String, dynamic&gt;?&gt; userFuture;<br><br>  @override<br>  void initState() {<br>    super.initState();<br>    userFuture = authService.getUserData();<br>  }<br><br>  Future&lt;void&gt; logout() async {<br>    await authService.logout();<br><br>    if (!mounted) return;<br><br>    Navigator.of(context, rootNavigator: true).pushNamedAndRemoveUntil(<br>      &#39;/login&#39;,<br>      (route) =&gt; false,<br>    );<br>  }<br><br>  @override<br>  Widget build(BuildContext context) {<br>    return Scaffold(<br>      backgroundColor: Colors.white,<br><br>      appBar: AppBar(<br>        backgroundColor: Colors.white,<br>        elevation: 0,<br>        title: const Text(<br>          &#39;Profile&#39;,<br>          style: TextStyle(<br>            color: Colors.black,<br>            fontWeight: FontWeight.bold,<br>            fontSize: 22,<br>          ),<br>        ),<br>        centerTitle: true,<br>      ),<br><br>      body: FutureBuilder&lt;Map&lt;String, dynamic&gt;?&gt;(<br>        future: userFuture,<br>        builder: (context, snapshot) {<br>          if (snapshot.hasError) {<br>            return const Center(child: Text(&#39;Gagal memuat data profil&#39;));<br>          }<br><br>          if (!snapshot.hasData || snapshot.data == null) {<br>            return const Center(child: Text(&#39;Data profil tidak ditemukan&#39;));<br>          }<br><br>          final data = snapshot.data!;<br><br>          final name = data[&#39;name&#39;] ?? &#39;-&#39;;<br>          final email = data[&#39;email&#39;] ?? &#39;-&#39;;<br>          final hobby = data[&#39;hobby&#39;] ?? &#39;-&#39;;<br>          final gender = data[&#39;gender&#39;] ?? &#39;Laki-Laki&#39;;<br>          final phone = data[&#39;phone&#39;] ?? &#39;-&#39;;<br>          final role = data[&#39;role&#39;] ?? &#39;Mahasiswa&#39;;<br><br>          final infoItems = [<br>            (<br>              Icons.mail_outline,<br>              &#39;Email&#39;,<br>              email,<br>              email != &#39;-&#39; ? &#39;mailto:$email&#39; : null,<br>            ),<br>            (<br>              Icons.favorite_border,<br>              &#39;Hobi&#39;,<br>              hobby,<br>              null,<br>            ),<br>            (<br>              Icons.phone_outlined,<br>              &#39;No Handphone&#39;,<br>              phone,<br>              phone != &#39;-&#39; ? &#39;tel:$phone&#39; : null,<br>            ),<br>          ];<br><br>          return SingleChildScrollView(<br>            padding: const EdgeInsets.symmetric(horizontal: 24),<br>            child: Column(<br>              children: [<br>                const SizedBox(height: 20),<br><br>                CircleAvatar(<br>                  radius: 65,<br>                  backgroundImage: AssetImage(&#39;assets/profil.jpg&#39;,),<br>                  backgroundColor: const Color(0xFFEFEFEF),<br>                ),<br><br>                const SizedBox(height: 20),<br><br>                Text(<br>                  name,<br>                  style: const TextStyle(<br>                    fontSize: 20,<br>                    fontWeight: FontWeight.bold,<br>                  ),<br>                ),<br><br>                const SizedBox(height: 8),<br><br>                Text(<br>                  role,<br>                  style: const TextStyle(<br>                    fontSize: 16,<br>                    color: Colors.black87,<br>                  ),<br>                ),<br><br>                const SizedBox(height: 40),<br><br>                ...infoItems.map(<br>                  (item) =&gt; _InfoCard(<br>                    icon: item.$1,<br>                    title: item.$2,<br>                    value: item.$3,<br>                    uriStr: item.$4,<br>                  ),<br>                ),<br><br>                const SizedBox(height: 20),<br><br>                SizedBox(<br>                  width: double.infinity,<br>                  height: 52,<br>                  child: ElevatedButton.icon(<br>                    onPressed: logout,<br>                    icon: const Icon(Icons.logout),<br>                    label: const Text(<br>                      &#39;Logout&#39;,<br>                      style: TextStyle(<br>                        fontSize: 16,<br>                        fontWeight: FontWeight.bold,<br>                      ),<br>                    ),<br>                    style: ElevatedButton.styleFrom(<br>                      backgroundColor: Colors.red,<br>                      foregroundColor: Colors.white,<br>                      shape: RoundedRectangleBorder(<br>                        borderRadius: BorderRadius.circular(12),<br>                      ),<br>                    ),<br>                  ),<br>                ),<br>              ],<br>            ),<br>          );<br>        },<br>      ),<br>    );<br>  }<br>}<br><br>class _InfoCard extends StatelessWidget {<br>  final IconData icon;<br>  final String title;<br>  final String value;<br>  final String? uriStr;<br><br>  const _InfoCard({<br>    required this.icon,<br>    required this.title,<br>    required this.value,<br>    this.uriStr,<br>  });<br><br>  Future&lt;void&gt; _launch(BuildContext context) async {<br>    if (uriStr == null) return;<br><br>    final uri = Uri.parse(uriStr!);<br><br>    try {<br>      final success = await launchUrl(<br>        uri,<br>        mode: LaunchMode.externalApplication,<br>      );<br><br>      if (!success &amp;&amp; context.mounted) {<br>        ScaffoldMessenger.of(context).showSnackBar(<br>          SnackBar(content: Text(&#39;Tidak dapat membuka: $value&#39;)),<br>        );<br>      }<br>    } catch (e) {<br>      if (context.mounted) {<br>        ScaffoldMessenger.of(context).showSnackBar(<br>          SnackBar(content: Text(&#39;Tidak dapat membuka: $value&#39;)),<br>        );<br>      }<br>    }<br>  }<br><br>  @override<br>  Widget build(BuildContext context) {<br>    return GestureDetector(<br>      onTap: uriStr != null ? () =&gt; _launch(context) : null,<br>      child: Container(<br>        margin: const EdgeInsets.only(bottom: 16),<br>        padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16),<br>        decoration: BoxDecoration(<br>          color: const Color(0xFFF6F6F6),<br>          borderRadius: BorderRadius.circular(12),<br>        ),<br>        child: Row(<br>          children: [<br>            Icon(icon, color: const Color(0xFF3B5998), size: 28),<br>            const SizedBox(width: 20),<br>            Flexible(<br>              child: Column(<br>                crossAxisAlignment: CrossAxisAlignment.start,<br>                children: [<br>                  Text(<br>                    title,<br>                    style: const TextStyle(<br>                      fontSize: 14,<br>                      fontWeight: FontWeight.bold,<br>                    ),<br>                  ),<br>                  const SizedBox(height: 4),<br>                  Text(<br>                    value,<br>                    style: const TextStyle(fontSize: 14, color: Colors.black87),<br>                    overflow: TextOverflow.ellipsis,<br>                    maxLines: 1,<br>                  ),<br>                ],<br>              ),<br>            ),<br>          ],<br>        ),<br>      ),<br>    );<br>  }<br>}</pre><h3><strong>Konfigurasi pada Firebase</strong></h3><p>Setelah kita menerapkan penggunaan firebase di project kita, terdapat langkah selanjutnya yang sangat penting. Langkah selanjutnya yaitu Konfigurasi pada Firebase kita. Untuk langkah-langkahnya adalah sebagai berikut :</p><h4><strong>Authentication</strong></h4><ul><li>Langkah yang pertama yaitu masuk ke <strong>Firebase Console &gt; Klik nama project &gt; Security &gt; Authentication &gt; Get started &gt; Sign-in method</strong></li><li>Kemudian aktifkan <strong>Email/Password</strong></li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/941/1*E9jpSl18jCrEIYup81h8tw.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/934/1*EBj14UFEWMyTduOiUkv5xA.png" /></figure><ul><li>Firebase sudah bisa dipakai untuk <strong>membuat akun</strong> dan <strong>login user</strong> dengan <strong>email dan password</strong>.</li><li>Tambahkan data user contoh dengan <strong>email (</strong><a href="/service/mailto:putri@gmail.com"><strong>putri@gmail.com</strong></a><strong>)</strong> dan <strong>password (putri123)</strong></li><li>Nanti hasilnya seperti ini</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/937/1*ph1ZonC2WlocMUqQRx7k1A.png" /></figure><h4><strong>Firestore Rules</strong></h4><p>Selanjutnya kita akan membuat peraturan yang harus ditaati saat ingin menyimpan data.</p><ul><li>Masuk ke <strong>Firestore &gt; Rules</strong></li><li>Masukkan kode di bawah ini</li></ul><pre>rules_version = &#39;2&#39;;<br><br>service cloud.firestore {<br>  match /databases/{database}/documents {<br><br>    match /users/{userId} {<br>      allow create: if request.auth != null<br>                    &amp;&amp; request.auth.uid == userId;<br><br>      allow read, update, delete: if request.auth != null<br>                                  &amp;&amp; request.auth.uid == userId;<br>    }<br>  }<br>}</pre><h4><strong>Membuat data untuk user (Optional tapi disarankan untuk awal)</strong></h4><ul><li>Langkah yang pertama yaitu masuk ke <strong>Firebase Console &gt; Klik nama project &gt; Databases &amp; Storage &gt; Firestore &gt; Create database</strong></li><li>Selanjutnya, kita akan membuat database. Pilih edisi <strong>Standard edition</strong> &gt; Pilih lokasi <strong>asia-southeast2 (Jakarta) </strong>&gt; Pilih <strong>Start in production mode</strong></li><li>Pilih <strong>Data &gt; Start collection</strong></li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/691/1*yY6RdsEjy0_UG5_KT81UQg.png" /></figure><ul><li>Untuk mengisi <strong>ID secara manual</strong>, kita perlu mendapatkan <strong>User UID</strong> dari user yang sudah dibuat</li><li>Kemudian isi struktur tabel sesuai data di bawah ini :</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/513/1*xe2wuNNJRrlcFkSi3dw3Rg.png" /></figure><ul><li>Nanti contoh hasilnya akan seperti ini</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/390/1*T0ze6BfljJf3p_ZiLEpxOw.png" /></figure><h3><strong>Hasil Proyek</strong></h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/497/1*tCYX59z6iQGfmIyIUnHKsQ.png" /></figure><p><strong>Selamat 🥳🥳🥳, karena sudah berhasil menghubungkan Aplikasi Flutter dengan Firebase</strong></p><p>Gimana nih teman-teman ? Apakah materinya seru ?<br>Semoga materi yang teman-teman pelajari hari ini dapat bermanfaat untuk kedepannya.</p><p><strong>See you on next time, Good Bye 🫡🤗✨</strong></p><h3><strong>Materi Bonus : Kondisi Foto Profil</strong></h3><p>Halo sobat Mobile Rangers, aku punya materi tambahan nih bagi kamu yang masih penasaran. Teman-teman sadar nggak sih bahwa gambar profil yang ditampilkan itu tetap ?</p><p>Nah, untuk itu aku akan kasih cara supaya gambarnya lumayan dinamis.</p><p><strong>Gambar Pengguna Perempuan</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/497/1*tCYX59z6iQGfmIyIUnHKsQ.png" /></figure><p><strong>Gambar Pengguna Laki-laki</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/503/1*3eyVp9p-GVYJRiqCREuq-A.png" /></figure><p>Caranya cukup mudah nih, teman-teman tinggal mengubah kode ini</p><pre>CircleAvatar(<br>  radius: 65,<br>  backgroundImage: AssetImage(&#39;assets/profil.jpg&#39;,),<br>  backgroundColor: const Color(0xFFEFEFEF),<br>),</pre><p><strong>Menjadi ini</strong></p><pre>CircleAvatar(<br>  radius: 65,<br>  backgroundImage: AssetImage(<br>    gender == &#39;Perempuan&#39;<br>      ? &#39;assets/female_profile.jpg&#39;<br>      : &#39;assets/male_profile.jpg&#39;,<br>    ),<br>  backgroundColor: const Color(0xFFEFEFEF),<br>),</pre><p>Selain itu, gambar <strong>profil.jpg</strong> diubah menjadi <strong>female_profile.jpg</strong> dan download file tambahan di bawah ini</p><p><a href="https://drive.google.com/file/d/1c4NRUkuMYzauMFTIhf0fG38EfcxtxZjs/view?usp=sharing"><strong>Link doksli</strong></a></p><p>Hasilnya nanti akan seperti ini :</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/208/1*r9S1dc3J-1ta6Ce3WDUUXw.png" /></figure><p><strong>Cara Kerja<br></strong>Pada saat login pengguna, sistem akan mengecek jenis kelaminnya (gender) untuk menentukan gambar yang ditampilkan (<strong>female_profile.jpg atau male_profile.jpg</strong>)</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a4e6c724e420" width="1" height="1" alt=""><hr><p><a href="https://medium.com/amcc-amikom/flutter-5-firebase-authentication-a4e6c724e420">Flutter #5 : Firebase Authentication</a> was originally published in <a href="https://medium.com/amcc-amikom">Amikom Computer Club</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>