<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://ahsai.my.id/feed.xml" rel="self" type="application/atom+xml" /><link href="https://ahsai.my.id/" rel="alternate" type="text/html" /><updated>2026-08-12T23:04:49+00:00</updated><id>https://ahsai.my.id/feed.xml</id><title type="html">Ahmad Saifullah</title><subtitle>Portfolio &amp; Blog — Software Developer, Trainer, Moslem Preneur, Urban Farmer</subtitle><entry><title type="html">Flutter #10: Validasi Form &amp;amp; SnackBar Feedback</title><link href="https://ahsai.my.id/blog/2026/08/12/flutter-10-validasi-form-snackbar-feedback/" rel="alternate" type="text/html" title="Flutter #10: Validasi Form &amp;amp; SnackBar Feedback" /><published>2026-08-12T00:00:00+00:00</published><updated>2026-08-12T00:00:00+00:00</updated><id>https://ahsai.my.id/blog/2026/08/12/flutter-10-validasi-form-snackbar-feedback</id><content type="html" xml:base="https://ahsai.my.id/blog/2026/08/12/flutter-10-validasi-form-snackbar-feedback/"><![CDATA[<p>Kemarin kita udah bikin <code class="language-plaintext highlighter-rouge">Form</code> + <code class="language-plaintext highlighter-rouge">TextFormField</code> dan sempat menyinggung <code class="language-plaintext highlighter-rouge">validator</code>. Tapi validasi yang bener itu bukan cuma “field nggak boleh kosong”. User itu kreatif — mereka bakal masukin email tanpa <code class="language-plaintext highlighter-rouge">@</code>, password cuma 3 huruf, atau konfirmasi password yang beda sama password aslinya. Tugas kamu: <strong>jangan percaya input user sampai lolos validasi</strong>, dan <strong>kasih feedback yang jelas</strong> kalau ada yang salah.</p>

<p>Hari ini kita bongkar tuntas validasi form + cara ngasih feedback lewat <strong>SnackBar</strong> yang user-friendly.</p>

<hr />

<h2 id="1-cara-kerja-validator">1. Cara Kerja <code class="language-plaintext highlighter-rouge">validator</code></h2>

<p>Di <code class="language-plaintext highlighter-rouge">TextFormField</code>, ada parameter <code class="language-plaintext highlighter-rouge">validator</code> yang berbentuk function:</p>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">validator:</span> <span class="p">(</span><span class="n">value</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">if</span> <span class="p">(</span><span class="n">value</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">||</span> <span class="n">value</span><span class="o">.</span><span class="na">trim</span><span class="p">()</span><span class="o">.</span><span class="na">isEmpty</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span> <span class="s">'Nggak boleh kosong!'</span><span class="p">;</span>
  <span class="p">}</span>
  <span class="k">return</span> <span class="kc">null</span><span class="p">;</span> <span class="c1">// ⬅️ null = VALID, nggak ada error</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>Aturan emas validator:</strong></p>
<ul>
  <li>Return <code class="language-plaintext highlighter-rouge">null</code> → field dianggap <strong>valid</strong> (nggak ada pesan error).</li>
  <li>Return <code class="language-plaintext highlighter-rouge">String</code> → field dianggap <strong>invalid</strong>, string itu ditampilkan merah di bawah field.</li>
  <li><code class="language-plaintext highlighter-rouge">value</code> bisa <code class="language-plaintext highlighter-rouge">null</code> kalau field dikosongkan — selalu cek <code class="language-plaintext highlighter-rouge">null</code> dulu!</li>
</ul>

<hr />

<h2 id="2-kumpulan-validator-yang-wajib-kamu-punya">2. Kumpulan Validator yang Wajib Kamu Punya</h2>

<p>Daripada nulis validasi berulang-ulang, bikin kumpulan validator reusable:</p>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">Validators</span> <span class="p">{</span>
  <span class="c1">// ✅ Wajib diisi</span>
  <span class="kd">static</span> <span class="kt">String</span><span class="o">?</span> <span class="kd">required</span><span class="p">(</span><span class="kt">String</span><span class="o">?</span> <span class="n">value</span><span class="p">,</span> <span class="p">[</span><span class="kt">String</span><span class="o">?</span> <span class="n">label</span><span class="p">])</span> <span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">value</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">||</span> <span class="n">value</span><span class="o">.</span><span class="na">trim</span><span class="p">()</span><span class="o">.</span><span class="na">isEmpty</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">return</span> <span class="s">'</span><span class="si">${label ?? 'Field ini'}</span><span class="s"> nggak boleh kosong!'</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="kc">null</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="c1">// ✅ Email pakai regex</span>
  <span class="kd">static</span> <span class="kt">String</span><span class="o">?</span> <span class="n">email</span><span class="p">(</span><span class="kt">String</span><span class="o">?</span> <span class="n">value</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">final</span> <span class="n">v</span> <span class="o">=</span> <span class="n">value</span><span class="o">?.</span><span class="na">trim</span><span class="p">()</span> <span class="o">??</span> <span class="s">''</span><span class="p">;</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">v</span><span class="o">.</span><span class="na">isEmpty</span><span class="p">)</span> <span class="k">return</span> <span class="s">'Email nggak boleh kosong!'</span><span class="p">;</span>
    <span class="kd">final</span> <span class="n">regex</span> <span class="o">=</span> <span class="n">RegExp</span><span class="p">(</span><span class="sx">r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$'</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">regex</span><span class="o">.</span><span class="na">hasMatch</span><span class="p">(</span><span class="n">v</span><span class="p">))</span> <span class="k">return</span> <span class="s">'Format email nggak valid!'</span><span class="p">;</span>
    <span class="k">return</span> <span class="kc">null</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="c1">// ✅ Minimal panjang</span>
  <span class="kd">static</span> <span class="kt">String</span><span class="o">?</span> <span class="n">minLength</span><span class="p">(</span><span class="kt">String</span><span class="o">?</span> <span class="n">value</span><span class="p">,</span> <span class="kt">int</span> <span class="n">min</span><span class="p">,</span> <span class="p">[</span><span class="kt">String</span><span class="o">?</span> <span class="n">label</span><span class="p">])</span> <span class="p">{</span>
    <span class="kd">final</span> <span class="n">v</span> <span class="o">=</span> <span class="n">value</span><span class="o">?.</span><span class="na">trim</span><span class="p">()</span> <span class="o">??</span> <span class="s">''</span><span class="p">;</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">v</span><span class="o">.</span><span class="na">isEmpty</span><span class="p">)</span> <span class="k">return</span> <span class="s">'</span><span class="si">${label ?? 'Field ini'}</span><span class="s"> nggak boleh kosong!'</span><span class="p">;</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">v</span><span class="o">.</span><span class="na">length</span> <span class="p">&lt;</span> <span class="n">min</span><span class="p">)</span> <span class="k">return</span> <span class="s">'Minimal </span><span class="si">$min</span><span class="s"> karakter!'</span><span class="p">;</span>
    <span class="k">return</span> <span class="kc">null</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="c1">// ✅ Konfirmasi password (butuh nilai field lain)</span>
  <span class="kd">static</span> <span class="kt">String</span><span class="o">?</span> <span class="n">match</span><span class="p">(</span><span class="kt">String</span><span class="o">?</span> <span class="n">value</span><span class="p">,</span> <span class="kt">String</span> <span class="n">other</span><span class="p">,</span> <span class="p">[</span><span class="kt">String</span><span class="o">?</span> <span class="n">label</span><span class="p">])</span> <span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">value</span> <span class="o">!=</span> <span class="n">other</span><span class="p">)</span> <span class="k">return</span> <span class="s">'</span><span class="si">${label ?? 'Konfirmasi'}</span><span class="s"> nggak cocok!'</span><span class="p">;</span>
    <span class="k">return</span> <span class="kc">null</span><span class="p">;</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Sekarang form kamu bersih banget:</p>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">TextFormField</span><span class="p">(</span>
  <span class="nl">decoration:</span> <span class="kd">const</span> <span class="n">InputDecoration</span><span class="p">(</span><span class="nl">labelText:</span> <span class="s">'Email'</span><span class="p">),</span>
  <span class="nl">validator:</span> <span class="n">Validators</span><span class="o">.</span><span class="na">email</span><span class="p">,</span>
<span class="p">),</span>
</code></pre></div></div>

<hr />

<h2 id="3-autovalidatemode--validasi-saat-mengetik">3. <code class="language-plaintext highlighter-rouge">autovalidateMode</code> — Validasi Saat Mengetik</h2>

<p>Default-nya, validator cuma jalan saat <code class="language-plaintext highlighter-rouge">.validate()</code> dipanggil (biasanya saat tombol submit ditekan). Kadang user pengen tau error-nya langsung tanpa nunggu tekan tombol. Di situlah <code class="language-plaintext highlighter-rouge">autovalidateMode</code> berperan:</p>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">TextFormField</span><span class="p">(</span>
  <span class="nl">autovalidateMode:</span> <span class="n">AutovalidateMode</span><span class="o">.</span><span class="na">onUserInteraction</span><span class="p">,</span>
  <span class="nl">validator:</span> <span class="n">Validators</span><span class="o">.</span><span class="na">email</span><span class="p">,</span>
<span class="p">),</span>
</code></pre></div></div>

<p><strong>Pilihan mode:</strong></p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">AutovalidateMode.disabled</code> — default, cuma validasi saat submit</li>
  <li><code class="language-plaintext highlighter-rouge">AutovalidateMode.onUserInteraction</code> — validasi tiap kali user berinteraksi (nulis/hapus) — <strong>paling recommended</strong></li>
  <li><code class="language-plaintext highlighter-rouge">AutovalidateMode.always</code> — validasi terus-menerus, bahkan sejak pertama build</li>
</ul>

<blockquote>
  <p><strong>Pro tip:</strong> Jangan pakai <code class="language-plaintext highlighter-rouge">always</code> kecuali memang perlu. Validasi yang nyala sebelum user sempat ngetik bikin form berasa “marah-marah” ke user.</p>
</blockquote>

<hr />

<h2 id="4-snackbar--feedback-yang-elegan">4. SnackBar — Feedback yang Elegan</h2>

<p>Setelah validasi lolos/gagal, kasih tau user hasilnya. <code class="language-plaintext highlighter-rouge">SnackBar</code> adalah notifikasi kecil yang muncul di bawah layar — nggak ngeblok interaksi kayak <code class="language-plaintext highlighter-rouge">AlertDialog</code>.</p>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Sukses ✅</span>
<span class="n">ScaffoldMessenger</span><span class="o">.</span><span class="na">of</span><span class="p">(</span><span class="n">context</span><span class="p">)</span><span class="o">.</span><span class="na">showSnackBar</span><span class="p">(</span>
  <span class="n">SnackBar</span><span class="p">(</span>
    <span class="nl">content:</span> <span class="n">Text</span><span class="p">(</span><span class="s">'✅ Registrasi berhasil! Selamat datang!'</span><span class="p">),</span>
    <span class="nl">backgroundColor:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">green</span><span class="p">,</span>
    <span class="nl">behavior:</span> <span class="n">SnackBarBehavior</span><span class="o">.</span><span class="na">floating</span><span class="p">,</span>
    <span class="nl">shape:</span> <span class="n">RoundedRectangleBorder</span><span class="p">(</span>
      <span class="nl">borderRadius:</span> <span class="n">BorderRadius</span><span class="o">.</span><span class="na">circular</span><span class="p">(</span><span class="mi">10</span><span class="p">),</span>
    <span class="p">),</span>
    <span class="nl">duration:</span> <span class="kd">const</span> <span class="n">Duration</span><span class="p">(</span><span class="nl">seconds:</span> <span class="mi">3</span><span class="p">),</span>
  <span class="p">),</span>
<span class="p">);</span>

<span class="c1">// Gagal ❌</span>
<span class="n">ScaffoldMessenger</span><span class="o">.</span><span class="na">of</span><span class="p">(</span><span class="n">context</span><span class="p">)</span><span class="o">.</span><span class="na">showSnackBar</span><span class="p">(</span>
  <span class="kd">const</span> <span class="n">SnackBar</span><span class="p">(</span>
    <span class="nl">content:</span> <span class="n">Text</span><span class="p">(</span><span class="s">'❌ Ada field yang masih salah, cek lagi ya!'</span><span class="p">),</span>
    <span class="nl">backgroundColor:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">red</span><span class="p">,</span>
  <span class="p">),</span>
<span class="p">);</span>
</code></pre></div></div>

<p><strong>Kenapa pakai <code class="language-plaintext highlighter-rouge">ScaffoldMessenger.of(context)</code>?</strong> Karena kalau pakai <code class="language-plaintext highlighter-rouge">Scaffold.of(context)</code> dan halaman sudah pindah/<code class="language-plaintext highlighter-rouge">Scaffold</code> udah ke-dispose, SnackBar bakal error. <code class="language-plaintext highlighter-rouge">ScaffoldMessenger</code> lebih aman — dia dikelola di level <code class="language-plaintext highlighter-rouge">MaterialApp</code>, jadi nggak keilangan referensi.</p>

<hr />

<h2 id="5-contoh-lengkap-form-registrasi-dengan-validasi-kuat">5. Contoh Lengkap: Form Registrasi dengan Validasi Kuat</h2>

<p>Gabungin semua jadi satu form register yang “production-ready”:</p>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="s">'package:flutter/material.dart'</span><span class="o">;</span>

<span class="kt">void</span> <span class="nf">main</span><span class="p">()</span> <span class="o">=</span><span class="p">&gt;</span> <span class="n">runApp</span><span class="p">(</span><span class="kd">const</span> <span class="n">MaterialApp</span><span class="p">(</span><span class="nl">home:</span> <span class="n">RegisterScreen</span><span class="p">()));</span>

<span class="kd">class</span> <span class="nc">RegisterScreen</span> <span class="kd">extends</span> <span class="n">StatefulWidget</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="n">RegisterScreen</span><span class="p">({</span><span class="k">super</span><span class="o">.</span><span class="na">key</span><span class="p">});</span>
  <span class="nd">@override</span>
  <span class="n">State</span><span class="p">&lt;</span><span class="n">RegisterScreen</span><span class="p">&gt;</span> <span class="n">createState</span><span class="p">()</span> <span class="o">=</span><span class="p">&gt;</span> <span class="n">_RegisterScreenState</span><span class="p">();</span>
<span class="p">}</span>

<span class="kd">class</span> <span class="nc">_RegisterScreenState</span> <span class="kd">extends</span> <span class="n">State</span><span class="p">&lt;</span><span class="n">RegisterScreen</span><span class="p">&gt;</span> <span class="p">{</span>
  <span class="kd">final</span> <span class="n">_formKey</span> <span class="o">=</span> <span class="n">GlobalKey</span><span class="p">&lt;</span><span class="n">FormState</span><span class="p">&gt;();</span>
  <span class="kd">final</span> <span class="n">_emailCtrl</span> <span class="o">=</span> <span class="n">TextEditingController</span><span class="p">();</span>
  <span class="kd">final</span> <span class="n">_passCtrl</span> <span class="o">=</span> <span class="n">TextEditingController</span><span class="p">();</span>
  <span class="kd">final</span> <span class="n">_confirmCtrl</span> <span class="o">=</span> <span class="n">TextEditingController</span><span class="p">();</span>

  <span class="nd">@override</span>
  <span class="kt">void</span> <span class="n">dispose</span><span class="p">()</span> <span class="p">{</span>
    <span class="n">_emailCtrl</span><span class="o">.</span><span class="na">dispose</span><span class="p">();</span>
    <span class="n">_passCtrl</span><span class="o">.</span><span class="na">dispose</span><span class="p">();</span>
    <span class="n">_confirmCtrl</span><span class="o">.</span><span class="na">dispose</span><span class="p">();</span>
    <span class="k">super</span><span class="o">.</span><span class="na">dispose</span><span class="p">();</span>
  <span class="p">}</span>

  <span class="kt">void</span> <span class="n">_submit</span><span class="p">()</span> <span class="p">{</span>
    <span class="c1">// 1️⃣ Validasi semua field</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">_formKey</span><span class="o">.</span><span class="na">currentState</span><span class="o">!.</span><span class="na">validate</span><span class="p">())</span> <span class="p">{</span>
      <span class="c1">// Feedback gagal</span>
      <span class="n">ScaffoldMessenger</span><span class="o">.</span><span class="na">of</span><span class="p">(</span><span class="n">context</span><span class="p">)</span><span class="o">.</span><span class="na">showSnackBar</span><span class="p">(</span>
        <span class="kd">const</span> <span class="n">SnackBar</span><span class="p">(</span>
          <span class="nl">content:</span> <span class="n">Text</span><span class="p">(</span><span class="s">'❌ Cek lagi field yang merah di atas!'</span><span class="p">),</span>
          <span class="nl">backgroundColor:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">red</span><span class="p">,</span>
        <span class="p">),</span>
      <span class="p">);</span>
      <span class="k">return</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="c1">// 2️⃣ Kalau lolos, save + proses</span>
    <span class="n">_formKey</span><span class="o">.</span><span class="na">currentState</span><span class="o">!.</span><span class="na">save</span><span class="p">();</span>
    <span class="n">ScaffoldMessenger</span><span class="o">.</span><span class="na">of</span><span class="p">(</span><span class="n">context</span><span class="p">)</span><span class="o">.</span><span class="na">showSnackBar</span><span class="p">(</span>
      <span class="n">SnackBar</span><span class="p">(</span>
        <span class="nl">content:</span> <span class="n">Text</span><span class="p">(</span><span class="s">'✅ Akun </span><span class="si">${_emailCtrl.text}</span><span class="s"> berhasil dibuat!'</span><span class="p">),</span>
        <span class="nl">backgroundColor:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">green</span><span class="p">,</span>
        <span class="nl">behavior:</span> <span class="n">SnackBarBehavior</span><span class="o">.</span><span class="na">floating</span><span class="p">,</span>
      <span class="p">),</span>
    <span class="p">);</span>
  <span class="p">}</span>

  <span class="nd">@override</span>
  <span class="n">Widget</span> <span class="n">build</span><span class="p">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span> <span class="n">Scaffold</span><span class="p">(</span>
      <span class="nl">appBar:</span> <span class="n">AppBar</span><span class="p">(</span><span class="nl">title:</span> <span class="kd">const</span> <span class="n">Text</span><span class="p">(</span><span class="s">'🔐 Register'</span><span class="p">)),</span>
      <span class="nl">body:</span> <span class="n">Padding</span><span class="p">(</span>
        <span class="nl">padding:</span> <span class="kd">const</span> <span class="n">EdgeInsets</span><span class="o">.</span><span class="na">all</span><span class="p">(</span><span class="mf">16.0</span><span class="p">),</span>
        <span class="nl">child:</span> <span class="n">Form</span><span class="p">(</span>
          <span class="nl">key:</span> <span class="n">_formKey</span><span class="p">,</span>
          <span class="nl">child:</span> <span class="n">Column</span><span class="p">(</span>
            <span class="nl">crossAxisAlignment:</span> <span class="n">CrossAxisAlignment</span><span class="o">.</span><span class="na">stretch</span><span class="p">,</span>
            <span class="nl">children:</span> <span class="p">[</span>
              <span class="n">TextFormField</span><span class="p">(</span>
                <span class="nl">controller:</span> <span class="n">_emailCtrl</span><span class="p">,</span>
                <span class="nl">keyboardType:</span> <span class="n">TextInputType</span><span class="o">.</span><span class="na">emailAddress</span><span class="p">,</span>
                <span class="nl">autovalidateMode:</span> <span class="n">AutovalidateMode</span><span class="o">.</span><span class="na">onUserInteraction</span><span class="p">,</span>
                <span class="nl">decoration:</span> <span class="kd">const</span> <span class="n">InputDecoration</span><span class="p">(</span>
                  <span class="nl">labelText:</span> <span class="s">'Email'</span><span class="p">,</span>
                  <span class="nl">icon:</span> <span class="n">Icon</span><span class="p">(</span><span class="n">Icons</span><span class="o">.</span><span class="na">email</span><span class="p">),</span>
                  <span class="nl">border:</span> <span class="n">OutlineInputBorder</span><span class="p">(),</span>
                <span class="p">),</span>
                <span class="nl">validator:</span> <span class="n">Validators</span><span class="o">.</span><span class="na">email</span><span class="p">,</span>
              <span class="p">),</span>
              <span class="kd">const</span> <span class="n">SizedBox</span><span class="p">(</span><span class="nl">height:</span> <span class="mi">16</span><span class="p">),</span>
              <span class="n">TextFormField</span><span class="p">(</span>
                <span class="nl">controller:</span> <span class="n">_passCtrl</span><span class="p">,</span>
                <span class="nl">obscureText:</span> <span class="kc">true</span><span class="p">,</span>
                <span class="nl">decoration:</span> <span class="kd">const</span> <span class="n">InputDecoration</span><span class="p">(</span>
                  <span class="nl">labelText:</span> <span class="s">'Password'</span><span class="p">,</span>
                  <span class="nl">icon:</span> <span class="n">Icon</span><span class="p">(</span><span class="n">Icons</span><span class="o">.</span><span class="na">lock</span><span class="p">),</span>
                  <span class="nl">border:</span> <span class="n">OutlineInputBorder</span><span class="p">(),</span>
                <span class="p">),</span>
                <span class="nl">validator:</span> <span class="p">(</span><span class="n">v</span><span class="p">)</span> <span class="o">=</span><span class="p">&gt;</span> <span class="n">Validators</span><span class="o">.</span><span class="na">minLength</span><span class="p">(</span><span class="n">v</span><span class="p">,</span> <span class="mi">6</span><span class="p">,</span> <span class="s">'Password'</span><span class="p">),</span>
              <span class="p">),</span>
              <span class="kd">const</span> <span class="n">SizedBox</span><span class="p">(</span><span class="nl">height:</span> <span class="mi">16</span><span class="p">),</span>
              <span class="n">TextFormField</span><span class="p">(</span>
                <span class="nl">controller:</span> <span class="n">_confirmCtrl</span><span class="p">,</span>
                <span class="nl">obscureText:</span> <span class="kc">true</span><span class="p">,</span>
                <span class="nl">autovalidateMode:</span> <span class="n">AutovalidateMode</span><span class="o">.</span><span class="na">onUserInteraction</span><span class="p">,</span>
                <span class="nl">decoration:</span> <span class="kd">const</span> <span class="n">InputDecoration</span><span class="p">(</span>
                  <span class="nl">labelText:</span> <span class="s">'Konfirmasi Password'</span><span class="p">,</span>
                  <span class="nl">icon:</span> <span class="n">Icon</span><span class="p">(</span><span class="n">Icons</span><span class="o">.</span><span class="na">lock_outline</span><span class="p">),</span>
                  <span class="nl">border:</span> <span class="n">OutlineInputBorder</span><span class="p">(),</span>
                <span class="p">),</span>
                <span class="nl">validator:</span> <span class="p">(</span><span class="n">v</span><span class="p">)</span> <span class="o">=</span><span class="p">&gt;</span>
                    <span class="n">Validators</span><span class="o">.</span><span class="na">match</span><span class="p">(</span><span class="n">v</span><span class="p">,</span> <span class="n">_passCtrl</span><span class="o">.</span><span class="na">text</span><span class="p">,</span> <span class="s">'Konfirmasi password'</span><span class="p">),</span>
              <span class="p">),</span>
              <span class="kd">const</span> <span class="n">SizedBox</span><span class="p">(</span><span class="nl">height:</span> <span class="mi">24</span><span class="p">),</span>
              <span class="n">ElevatedButton</span><span class="o">.</span><span class="na">icon</span><span class="p">(</span>
                <span class="nl">onPressed:</span> <span class="n">_submit</span><span class="p">,</span>
                <span class="nl">icon:</span> <span class="kd">const</span> <span class="n">Icon</span><span class="p">(</span><span class="n">Icons</span><span class="o">.</span><span class="na">check_circle</span><span class="p">),</span>
                <span class="nl">label:</span> <span class="kd">const</span> <span class="n">Padding</span><span class="p">(</span>
                  <span class="nl">padding:</span> <span class="n">EdgeInsets</span><span class="o">.</span><span class="na">symmetric</span><span class="p">(</span><span class="nl">vertical:</span> <span class="mi">12</span><span class="p">),</span>
                  <span class="nl">child:</span> <span class="n">Text</span><span class="p">(</span><span class="s">'Daftar'</span><span class="p">),</span>
                <span class="p">),</span>
              <span class="p">),</span>
            <span class="p">],</span>
          <span class="p">),</span>
        <span class="p">),</span>
      <span class="p">),</span>
    <span class="p">);</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>Jalankan dan coba:</strong></p>
<ol>
  <li>Tekan “Daftar” tanpa isi apa-apa → semua field merah + SnackBar merah muncul.</li>
  <li>Isi email <code class="language-plaintext highlighter-rouge">abc</code> → langsung muncul “Format email nggak valid!” saat ngetik (karena <code class="language-plaintext highlighter-rouge">onUserInteraction</code>).</li>
  <li>Password <code class="language-plaintext highlighter-rouge">123</code>, konfirmasi <code class="language-plaintext highlighter-rouge">456</code> → error “Konfirmasi password nggak cocok!”.</li>
  <li>Isi semua dengan benar → SnackBar hijau “berhasil dibuat!”.</li>
</ol>

<hr />

<h2 id="6-styling-pesan-error">6. Styling Pesan Error</h2>

<p>Pesan error default Flutter udah bagus, tapi kamu bisa ubah:</p>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">TextFormField</span><span class="p">(</span>
  <span class="nl">decoration:</span> <span class="n">InputDecoration</span><span class="p">(</span>
    <span class="nl">labelText:</span> <span class="s">'Email'</span><span class="p">,</span>
    <span class="nl">errorStyle:</span> <span class="kd">const</span> <span class="n">TextStyle</span><span class="p">(</span><span class="nl">color:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">orange</span><span class="p">,</span> <span class="nl">fontSize:</span> <span class="mi">12</span><span class="p">),</span>
    <span class="nl">errorBorder:</span> <span class="n">OutlineInputBorder</span><span class="p">(</span>
      <span class="nl">borderSide:</span> <span class="kd">const</span> <span class="n">BorderSide</span><span class="p">(</span><span class="nl">color:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">orange</span><span class="p">,</span> <span class="nl">width:</span> <span class="mi">2</span><span class="p">),</span>
      <span class="nl">borderRadius:</span> <span class="n">BorderRadius</span><span class="o">.</span><span class="na">circular</span><span class="p">(</span><span class="mi">8</span><span class="p">),</span>
    <span class="p">),</span>
    <span class="nl">focusedErrorBorder:</span> <span class="n">OutlineInputBorder</span><span class="p">(</span>
      <span class="nl">borderSide:</span> <span class="kd">const</span> <span class="n">BorderSide</span><span class="p">(</span><span class="nl">color:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">red</span><span class="p">,</span> <span class="nl">width:</span> <span class="mi">2</span><span class="p">),</span>
      <span class="nl">borderRadius:</span> <span class="n">BorderRadius</span><span class="o">.</span><span class="na">circular</span><span class="p">(</span><span class="mi">8</span><span class="p">),</span>
    <span class="p">),</span>
  <span class="p">),</span>
  <span class="nl">validator:</span> <span class="n">Validators</span><span class="o">.</span><span class="na">email</span><span class="p">,</span>
<span class="p">)</span>
</code></pre></div></div>

<ul>
  <li><code class="language-plaintext highlighter-rouge">errorStyle</code> — styling teks error</li>
  <li><code class="language-plaintext highlighter-rouge">errorBorder</code> — border field saat error (nggak fokus)</li>
  <li><code class="language-plaintext highlighter-rouge">focusedErrorBorder</code> — border field saat error DAN sedang fokus</li>
</ul>

<hr />

<h2 id="ringkasan">Ringkasan</h2>

<ul>
  <li><strong><code class="language-plaintext highlighter-rouge">validator</code></strong> return <code class="language-plaintext highlighter-rouge">null</code> (valid) atau <code class="language-plaintext highlighter-rouge">String</code> (pesan error)</li>
  <li><strong>Bikin validator reusable</strong> — jangan nulis ulang tiap form</li>
  <li><strong><code class="language-plaintext highlighter-rouge">autovalidateMode.onUserInteraction</code></strong> — validasi real-time tanpa bikin user kesel</li>
  <li><strong><code class="language-plaintext highlighter-rouge">SnackBar</code></strong> — feedback elegan, pakai <code class="language-plaintext highlighter-rouge">ScaffoldMessenger.of(context)</code> biar aman</li>
  <li><strong>Pola baku</strong>: <code class="language-plaintext highlighter-rouge">validate()</code> → gagal (SnackBar merah) / lolos (SnackBar hijau + proses data)</li>
</ul>

<p>Validasi + feedback yang bener bikin aplikasimu berasa <strong>profesional dan peduli user</strong>. Ini skill yang bakal kepakai di semua project nyata — dari login sampai form checkout.</p>

<p>Besok kita lanjut ke <strong>Asset &amp; Image — Gambar Lokal &amp; Network</strong>. Siap-siap bikin tampilan makin hidup! 🎨</p>

<p><strong>Coba sendiri! Bikin form login (email + password) dengan validasi lengkap dan SnackBar. Share hasilnya ke sosial media dan tag @ahsai001!</strong></p>]]></content><author><name></name></author><category term="flutter" /><category term="dart" /><category term="form" /><category term="validasi" /><category term="snackbar" /><category term="tutorial" /><category term="indonesia" /><category term="pemrograman" /><summary type="html"><![CDATA[Kemarin kita udah bikin Form + TextFormField dan sempat menyinggung validator. Tapi validasi yang bener itu bukan cuma “field nggak boleh kosong”. User itu kreatif — mereka bakal masukin email tanpa @, password cuma 3 huruf, atau konfirmasi password yang beda sama password aslinya. Tugas kamu: jangan percaya input user sampai lolos validasi, dan kasih feedback yang jelas kalau ada yang salah.]]></summary></entry><entry><title type="html">Flutter #9: Form &amp;amp; TextField — Input dari User</title><link href="https://ahsai.my.id/blog/2026/08/11/flutter-9-form-textfield-input-user/" rel="alternate" type="text/html" title="Flutter #9: Form &amp;amp; TextField — Input dari User" /><published>2026-08-11T00:00:00+00:00</published><updated>2026-08-11T00:00:00+00:00</updated><id>https://ahsai.my.id/blog/2026/08/11/flutter-9-form-textfield-input-user</id><content type="html" xml:base="https://ahsai.my.id/blog/2026/08/11/flutter-9-form-textfield-input-user/"><![CDATA[<p>Aplikasi yang cuma bisa tampil doang itu kayak TV tanpa remote — cuma bisa dilihat, nggak bisa diajak interaksi. Realitasnya, hampir semua aplikasi butuh <strong>input dari user</strong>: login, register, search, chat, notes, form order, dll. Di Flutter, senjata utamanya ada dua: <strong>TextField</strong> (input satuan) dan <strong>Form</strong> (kumpulan input dengan validasi).</p>

<p>Hari ini kita bahas cara menerima input dari user dengan benar. Mulai dari TextField sederhana sampai Form multi-field yang siap divalidasi (validasi lengkapnya besok, ya!).</p>

<hr />

<h2 id="1-textfield--input-satuan">1. TextField — Input Satuan</h2>

<p><code class="language-plaintext highlighter-rouge">TextField</code> adalah widget paling dasar untuk menerima teks dari user. Mirip <code class="language-plaintext highlighter-rouge">&lt;input&gt;</code> di HTML. Contoh paling sederhana:</p>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="s">'package:flutter/material.dart'</span><span class="o">;</span>

<span class="kt">void</span> <span class="nf">main</span><span class="p">()</span> <span class="o">=</span><span class="p">&gt;</span> <span class="n">runApp</span><span class="p">(</span><span class="kd">const</span> <span class="n">MaterialApp</span><span class="p">(</span><span class="nl">home:</span> <span class="n">InputSederhana</span><span class="p">()));</span>

<span class="kd">class</span> <span class="nc">InputSederhana</span> <span class="kd">extends</span> <span class="n">StatefulWidget</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="n">InputSederhana</span><span class="p">({</span><span class="k">super</span><span class="o">.</span><span class="na">key</span><span class="p">});</span>

  <span class="nd">@override</span>
  <span class="n">State</span><span class="p">&lt;</span><span class="n">InputSederhana</span><span class="p">&gt;</span> <span class="n">createState</span><span class="p">()</span> <span class="o">=</span><span class="p">&gt;</span> <span class="n">_InputSederhanaState</span><span class="p">();</span>
<span class="p">}</span>

<span class="kd">class</span> <span class="nc">_InputSederhanaState</span> <span class="kd">extends</span> <span class="n">State</span><span class="p">&lt;</span><span class="n">InputSederhana</span><span class="p">&gt;</span> <span class="p">{</span>
  <span class="c1">// 🔑 Controller = jembatan antara TextField dan logic kita</span>
  <span class="kd">final</span> <span class="n">TextEditingController</span> <span class="n">_controller</span> <span class="o">=</span> <span class="n">TextEditingController</span><span class="p">();</span>

  <span class="nd">@override</span>
  <span class="kt">void</span> <span class="n">dispose</span><span class="p">()</span> <span class="p">{</span>
    <span class="n">_controller</span><span class="o">.</span><span class="na">dispose</span><span class="p">();</span> <span class="c1">// WAJIB: bersihin controller biar nggak bocor memori</span>
    <span class="k">super</span><span class="o">.</span><span class="na">dispose</span><span class="p">();</span>
  <span class="p">}</span>

  <span class="nd">@override</span>
  <span class="n">Widget</span> <span class="n">build</span><span class="p">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span> <span class="n">Scaffold</span><span class="p">(</span>
      <span class="nl">appBar:</span> <span class="n">AppBar</span><span class="p">(</span><span class="nl">title:</span> <span class="kd">const</span> <span class="n">Text</span><span class="p">(</span><span class="s">'📝 Input Sederhana'</span><span class="p">)),</span>
      <span class="nl">body:</span> <span class="n">Padding</span><span class="p">(</span>
        <span class="nl">padding:</span> <span class="kd">const</span> <span class="n">EdgeInsets</span><span class="o">.</span><span class="na">all</span><span class="p">(</span><span class="mf">16.0</span><span class="p">),</span>
        <span class="nl">child:</span> <span class="n">Column</span><span class="p">(</span>
          <span class="nl">children:</span> <span class="p">[</span>
            <span class="n">TextField</span><span class="p">(</span>
              <span class="nl">controller:</span> <span class="n">_controller</span><span class="p">,</span>
              <span class="nl">decoration:</span> <span class="kd">const</span> <span class="n">InputDecoration</span><span class="p">(</span>
                <span class="nl">labelText:</span> <span class="s">'Nama kamu'</span><span class="p">,</span>
                <span class="nl">hintText:</span> <span class="s">'Ketik di sini...'</span><span class="p">,</span>
                <span class="nl">border:</span> <span class="n">OutlineInputBorder</span><span class="p">(),</span>
              <span class="p">),</span>
            <span class="p">),</span>
            <span class="kd">const</span> <span class="n">SizedBox</span><span class="p">(</span><span class="nl">height:</span> <span class="mi">16</span><span class="p">),</span>
            <span class="n">ElevatedButton</span><span class="p">(</span>
              <span class="nl">onPressed:</span> <span class="p">()</span> <span class="p">{</span>
                <span class="c1">// 📍 Ambil teks dari controller</span>
                <span class="kd">final</span> <span class="n">nama</span> <span class="o">=</span> <span class="n">_controller</span><span class="o">.</span><span class="na">text</span><span class="p">;</span>
                <span class="k">if</span> <span class="p">(</span><span class="n">nama</span><span class="o">.</span><span class="na">isNotEmpty</span><span class="p">)</span> <span class="p">{</span>
                  <span class="n">ScaffoldMessenger</span><span class="o">.</span><span class="na">of</span><span class="p">(</span><span class="n">context</span><span class="p">)</span><span class="o">.</span><span class="na">showSnackBar</span><span class="p">(</span>
                    <span class="n">SnackBar</span><span class="p">(</span><span class="nl">content:</span> <span class="n">Text</span><span class="p">(</span><span class="s">'Halo, </span><span class="si">$nama</span><span class="s">! 👋'</span><span class="p">)),</span>
                  <span class="p">);</span>
                <span class="p">}</span>
              <span class="p">},</span>
              <span class="nl">child:</span> <span class="kd">const</span> <span class="n">Text</span><span class="p">(</span><span class="s">'Sapa Aku!'</span><span class="p">),</span>
            <span class="p">),</span>
          <span class="p">],</span>
        <span class="p">),</span>
      <span class="p">),</span>
    <span class="p">);</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>Yang wajib kamu ingat:</strong></p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">TextEditingController</code> — jembatan baca/tulis ke TextField</li>
  <li><code class="language-plaintext highlighter-rouge">dispose()</code> — selalu panggil di <code class="language-plaintext highlighter-rouge">dispose()</code> state, kalau nggak → memory leak</li>
  <li><code class="language-plaintext highlighter-rouge">decoration</code> — styling placeholder, label, border, icon, dll</li>
</ul>

<h3 id="textfield-properties-yang-sering-dipakai">TextField Properties yang Sering Dipakai</h3>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">TextField</span><span class="p">(</span>
  <span class="nl">controller:</span> <span class="n">_controller</span><span class="p">,</span>
  <span class="nl">obscureText:</span> <span class="kc">true</span><span class="p">,</span>          <span class="c1">// password mode (••••••)</span>
  <span class="nl">maxLines:</span> <span class="mi">5</span><span class="p">,</span>                <span class="c1">// textarea (default 1 = single line)</span>
  <span class="nl">keyboardType:</span> <span class="n">TextInputType</span><span class="o">.</span><span class="na">emailAddress</span><span class="p">,</span>  <span class="c1">// keyboard @ muncul</span>
  <span class="nl">textInputAction:</span> <span class="n">TextInputAction</span><span class="o">.</span><span class="na">done</span><span class="p">,</span>     <span class="c1">// tombol "Done" di keyboard</span>
  <span class="nl">onChanged:</span> <span class="p">(</span><span class="n">value</span><span class="p">)</span> <span class="p">{</span>        <span class="c1">// real-time listener, setiap ketikan</span>
    <span class="n">print</span><span class="p">(</span><span class="s">'User mengetik: </span><span class="si">$value</span><span class="s">'</span><span class="p">);</span>
  <span class="p">},</span>
  <span class="nl">onSubmitted:</span> <span class="p">(</span><span class="n">value</span><span class="p">)</span> <span class="p">{</span>     <span class="c1">// user tekan Enter/Done</span>
    <span class="n">print</span><span class="p">(</span><span class="s">'User submit: </span><span class="si">$value</span><span class="s">'</span><span class="p">);</span>
  <span class="p">},</span>
  <span class="nl">decoration:</span> <span class="kd">const</span> <span class="n">InputDecoration</span><span class="p">(</span>
    <span class="nl">icon:</span> <span class="n">Icon</span><span class="p">(</span><span class="n">Icons</span><span class="o">.</span><span class="na">person</span><span class="p">),</span>    <span class="c1">// icon di kiri</span>
    <span class="nl">labelText:</span> <span class="s">'Email'</span><span class="p">,</span>
    <span class="nl">hintText:</span> <span class="s">'contoh@email.com'</span><span class="p">,</span>
    <span class="nl">helperText:</span> <span class="s">'Masukkan email aktif'</span><span class="p">,</span>
    <span class="nl">prefixText:</span> <span class="s">'@'</span><span class="p">,</span>             <span class="c1">// teks sebelum input</span>
    <span class="nl">suffixIcon:</span> <span class="n">Icon</span><span class="p">(</span><span class="n">Icons</span><span class="o">.</span><span class="na">check</span><span class="p">),</span>
  <span class="p">),</span>
<span class="p">)</span>
</code></pre></div></div>

<hr />

<h2 id="2-form-widget--multi-field-terstruktur">2. Form Widget — Multi-Field Terstruktur</h2>

<p>Kalau cuma satu input, <code class="language-plaintext highlighter-rouge">TextField</code> cukup. Tapi begitu kamu punya 3-4 field (misal: nama, email, password, alamat), lebih rapi pakai widget <code class="language-plaintext highlighter-rouge">Form</code>.</p>

<p><code class="language-plaintext highlighter-rouge">Form</code> adalah <strong>container</strong> yang membungkus beberapa <code class="language-plaintext highlighter-rouge">TextFormField</code> (adiknya TextField). <code class="language-plaintext highlighter-rouge">Form</code> punya <code class="language-plaintext highlighter-rouge">GlobalKey&lt;FormState&gt;</code> yang bisa kamu pakai untuk <strong>validasi, reset, atau save</strong> semua field sekaligus.</p>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="s">'package:flutter/material.dart'</span><span class="o">;</span>

<span class="kt">void</span> <span class="nf">main</span><span class="p">()</span> <span class="o">=</span><span class="p">&gt;</span> <span class="n">runApp</span><span class="p">(</span><span class="kd">const</span> <span class="n">MaterialApp</span><span class="p">(</span><span class="nl">home:</span> <span class="n">FormRegisterScreen</span><span class="p">()));</span>

<span class="kd">class</span> <span class="nc">FormRegisterScreen</span> <span class="kd">extends</span> <span class="n">StatefulWidget</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="n">FormRegisterScreen</span><span class="p">({</span><span class="k">super</span><span class="o">.</span><span class="na">key</span><span class="p">});</span>

  <span class="nd">@override</span>
  <span class="n">State</span><span class="p">&lt;</span><span class="n">FormRegisterScreen</span><span class="p">&gt;</span> <span class="n">createState</span><span class="p">()</span> <span class="o">=</span><span class="p">&gt;</span> <span class="n">_FormRegisterScreenState</span><span class="p">();</span>
<span class="p">}</span>

<span class="kd">class</span> <span class="nc">_FormRegisterScreenState</span> <span class="kd">extends</span> <span class="n">State</span><span class="p">&lt;</span><span class="n">FormRegisterScreen</span><span class="p">&gt;</span> <span class="p">{</span>
  <span class="c1">// 🔑 Kunci form — wajib untuk akses FormState</span>
  <span class="kd">final</span> <span class="n">_formKey</span> <span class="o">=</span> <span class="n">GlobalKey</span><span class="p">&lt;</span><span class="n">FormState</span><span class="p">&gt;();</span>

  <span class="c1">// 🗃️ Controller buat ambil nilai user</span>
  <span class="kd">final</span> <span class="n">_namaCtrl</span> <span class="o">=</span> <span class="n">TextEditingController</span><span class="p">();</span>
  <span class="kd">final</span> <span class="n">_emailCtrl</span> <span class="o">=</span> <span class="n">TextEditingController</span><span class="p">();</span>
  <span class="kd">final</span> <span class="n">_passCtrl</span> <span class="o">=</span> <span class="n">TextEditingController</span><span class="p">();</span>

  <span class="nd">@override</span>
  <span class="kt">void</span> <span class="n">dispose</span><span class="p">()</span> <span class="p">{</span>
    <span class="n">_namaCtrl</span><span class="o">.</span><span class="na">dispose</span><span class="p">();</span>
    <span class="n">_emailCtrl</span><span class="o">.</span><span class="na">dispose</span><span class="p">();</span>
    <span class="n">_passCtrl</span><span class="o">.</span><span class="na">dispose</span><span class="p">();</span>
    <span class="k">super</span><span class="o">.</span><span class="na">dispose</span><span class="p">();</span>
  <span class="p">}</span>

  <span class="kt">void</span> <span class="n">_submitForm</span><span class="p">()</span> <span class="p">{</span>
    <span class="c1">// Cek validasi semua field dulu</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">_formKey</span><span class="o">.</span><span class="na">currentState</span><span class="o">!.</span><span class="na">validate</span><span class="p">())</span> <span class="p">{</span>
      <span class="c1">// Ambil data</span>
      <span class="kd">final</span> <span class="n">nama</span> <span class="o">=</span> <span class="n">_namaCtrl</span><span class="o">.</span><span class="na">text</span><span class="p">;</span>
      <span class="kd">final</span> <span class="n">email</span> <span class="o">=</span> <span class="n">_emailCtrl</span><span class="o">.</span><span class="na">text</span><span class="p">;</span>

      <span class="n">ScaffoldMessenger</span><span class="o">.</span><span class="na">of</span><span class="p">(</span><span class="n">context</span><span class="p">)</span><span class="o">.</span><span class="na">showSnackBar</span><span class="p">(</span>
        <span class="n">SnackBar</span><span class="p">(</span>
          <span class="nl">content:</span> <span class="n">Text</span><span class="p">(</span><span class="s">'✅ Akun </span><span class="si">$nama</span><span class="s"> (</span><span class="si">$email</span><span class="s">) berhasil dibuat!'</span><span class="p">),</span>
          <span class="nl">backgroundColor:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">green</span><span class="p">,</span>
        <span class="p">),</span>
      <span class="p">);</span>

      <span class="c1">// Reset form setelah submit</span>
      <span class="n">_formKey</span><span class="o">.</span><span class="na">currentState</span><span class="o">!.</span><span class="na">reset</span><span class="p">();</span>
    <span class="p">}</span>
  <span class="p">}</span>

  <span class="nd">@override</span>
  <span class="n">Widget</span> <span class="n">build</span><span class="p">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span> <span class="n">Scaffold</span><span class="p">(</span>
      <span class="nl">appBar:</span> <span class="n">AppBar</span><span class="p">(</span><span class="nl">title:</span> <span class="kd">const</span> <span class="n">Text</span><span class="p">(</span><span class="s">'📋 Form Registrasi'</span><span class="p">)),</span>
      <span class="nl">body:</span> <span class="n">Padding</span><span class="p">(</span>
        <span class="nl">padding:</span> <span class="kd">const</span> <span class="n">EdgeInsets</span><span class="o">.</span><span class="na">all</span><span class="p">(</span><span class="mf">16.0</span><span class="p">),</span>
        <span class="nl">child:</span> <span class="n">Form</span><span class="p">(</span>
          <span class="nl">key:</span> <span class="n">_formKey</span><span class="p">,</span>
          <span class="nl">child:</span> <span class="n">Column</span><span class="p">(</span>
            <span class="nl">crossAxisAlignment:</span> <span class="n">CrossAxisAlignment</span><span class="o">.</span><span class="na">stretch</span><span class="p">,</span>
            <span class="nl">children:</span> <span class="p">[</span>
              <span class="c1">// === FIELD 1: NAMA ===</span>
              <span class="n">TextFormField</span><span class="p">(</span>
                <span class="nl">controller:</span> <span class="n">_namaCtrl</span><span class="p">,</span>
                <span class="nl">decoration:</span> <span class="kd">const</span> <span class="n">InputDecoration</span><span class="p">(</span>
                  <span class="nl">labelText:</span> <span class="s">'Nama Lengkap'</span><span class="p">,</span>
                  <span class="nl">icon:</span> <span class="n">Icon</span><span class="p">(</span><span class="n">Icons</span><span class="o">.</span><span class="na">person</span><span class="p">),</span>
                  <span class="nl">border:</span> <span class="n">OutlineInputBorder</span><span class="p">(),</span>
                <span class="p">),</span>
                <span class="nl">validator:</span> <span class="p">(</span><span class="n">value</span><span class="p">)</span> <span class="p">{</span>
                  <span class="k">if</span> <span class="p">(</span><span class="n">value</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">||</span> <span class="n">value</span><span class="o">.</span><span class="na">trim</span><span class="p">()</span><span class="o">.</span><span class="na">isEmpty</span><span class="p">)</span> <span class="p">{</span>
                    <span class="k">return</span> <span class="s">'❌ Nama nggak boleh kosong!'</span><span class="p">;</span>
                  <span class="p">}</span>
                  <span class="k">return</span> <span class="kc">null</span><span class="p">;</span>
                <span class="p">},</span>
              <span class="p">),</span>

              <span class="kd">const</span> <span class="n">SizedBox</span><span class="p">(</span><span class="nl">height:</span> <span class="mi">16</span><span class="p">),</span>

              <span class="c1">// === FIELD 2: EMAIL ===</span>
              <span class="n">TextFormField</span><span class="p">(</span>
                <span class="nl">controller:</span> <span class="n">_emailCtrl</span><span class="p">,</span>
                <span class="nl">keyboardType:</span> <span class="n">TextInputType</span><span class="o">.</span><span class="na">emailAddress</span><span class="p">,</span>
                <span class="nl">decoration:</span> <span class="kd">const</span> <span class="n">InputDecoration</span><span class="p">(</span>
                  <span class="nl">labelText:</span> <span class="s">'Email'</span><span class="p">,</span>
                  <span class="nl">icon:</span> <span class="n">Icon</span><span class="p">(</span><span class="n">Icons</span><span class="o">.</span><span class="na">email</span><span class="p">),</span>
                  <span class="nl">border:</span> <span class="n">OutlineInputBorder</span><span class="p">(),</span>
                <span class="p">),</span>
                <span class="nl">validator:</span> <span class="p">(</span><span class="n">value</span><span class="p">)</span> <span class="p">{</span>
                  <span class="k">if</span> <span class="p">(</span><span class="n">value</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">||</span> <span class="n">value</span><span class="o">.</span><span class="na">trim</span><span class="p">()</span><span class="o">.</span><span class="na">isEmpty</span><span class="p">)</span> <span class="p">{</span>
                    <span class="k">return</span> <span class="s">'❌ Email nggak boleh kosong!'</span><span class="p">;</span>
                  <span class="p">}</span>
                  <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">value</span><span class="o">.</span><span class="na">contains</span><span class="p">(</span><span class="s">'@'</span><span class="p">)</span> <span class="o">||</span> <span class="o">!</span><span class="n">value</span><span class="o">.</span><span class="na">contains</span><span class="p">(</span><span class="s">'.'</span><span class="p">))</span> <span class="p">{</span>
                    <span class="k">return</span> <span class="s">'❌ Format email nggak valid!'</span><span class="p">;</span>
                  <span class="p">}</span>
                  <span class="k">return</span> <span class="kc">null</span><span class="p">;</span>
                <span class="p">},</span>
              <span class="p">),</span>

              <span class="kd">const</span> <span class="n">SizedBox</span><span class="p">(</span><span class="nl">height:</span> <span class="mi">16</span><span class="p">),</span>

              <span class="c1">// === FIELD 3: PASSWORD ===</span>
              <span class="n">TextFormField</span><span class="p">(</span>
                <span class="nl">controller:</span> <span class="n">_passCtrl</span><span class="p">,</span>
                <span class="nl">obscureText:</span> <span class="kc">true</span><span class="p">,</span>
                <span class="nl">decoration:</span> <span class="kd">const</span> <span class="n">InputDecoration</span><span class="p">(</span>
                  <span class="nl">labelText:</span> <span class="s">'Password'</span><span class="p">,</span>
                  <span class="nl">icon:</span> <span class="n">Icon</span><span class="p">(</span><span class="n">Icons</span><span class="o">.</span><span class="na">lock</span><span class="p">),</span>
                  <span class="nl">border:</span> <span class="n">OutlineInputBorder</span><span class="p">(),</span>
                <span class="p">),</span>
                <span class="nl">validator:</span> <span class="p">(</span><span class="n">value</span><span class="p">)</span> <span class="p">{</span>
                  <span class="k">if</span> <span class="p">(</span><span class="n">value</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">||</span> <span class="n">value</span><span class="o">.</span><span class="na">isEmpty</span><span class="p">)</span> <span class="p">{</span>
                    <span class="k">return</span> <span class="s">'❌ Password nggak boleh kosong!'</span><span class="p">;</span>
                  <span class="p">}</span>
                  <span class="k">if</span> <span class="p">(</span><span class="n">value</span><span class="o">.</span><span class="na">length</span> <span class="p">&lt;</span> <span class="mi">6</span><span class="p">)</span> <span class="p">{</span>
                    <span class="k">return</span> <span class="s">'❌ Password minimal 6 karakter!'</span><span class="p">;</span>
                  <span class="p">}</span>
                  <span class="k">return</span> <span class="kc">null</span><span class="p">;</span>
                <span class="p">},</span>
              <span class="p">),</span>

              <span class="kd">const</span> <span class="n">SizedBox</span><span class="p">(</span><span class="nl">height:</span> <span class="mi">24</span><span class="p">),</span>

              <span class="c1">// === TOMBOL SUBMIT ===</span>
              <span class="n">ElevatedButton</span><span class="o">.</span><span class="na">icon</span><span class="p">(</span>
                <span class="nl">onPressed:</span> <span class="n">_submitForm</span><span class="p">,</span>
                <span class="nl">icon:</span> <span class="kd">const</span> <span class="n">Icon</span><span class="p">(</span><span class="n">Icons</span><span class="o">.</span><span class="na">check_circle</span><span class="p">),</span>
                <span class="nl">label:</span> <span class="kd">const</span> <span class="n">Text</span><span class="p">(</span><span class="s">'Daftar'</span><span class="p">),</span>
                <span class="nl">style:</span> <span class="n">ElevatedButton</span><span class="o">.</span><span class="na">styleFrom</span><span class="p">(</span>
                  <span class="nl">padding:</span> <span class="kd">const</span> <span class="n">EdgeInsets</span><span class="o">.</span><span class="na">symmetric</span><span class="p">(</span><span class="nl">vertical:</span> <span class="mi">16</span><span class="p">),</span>
                <span class="p">),</span>
              <span class="p">),</span>
            <span class="p">],</span>
          <span class="p">),</span>
        <span class="p">),</span>
      <span class="p">),</span>
    <span class="p">);</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>Apa yang terjadi di kode di atas:</strong></p>

<ol>
  <li><code class="language-plaintext highlighter-rouge">GlobalKey&lt;FormState&gt;</code> — kunci ajaib yang ngasih kamu akses <code class="language-plaintext highlighter-rouge">.validate()</code>, <code class="language-plaintext highlighter-rouge">.reset()</code>, <code class="language-plaintext highlighter-rouge">.save()</code> ke seluruh form</li>
  <li><code class="language-plaintext highlighter-rouge">TextFormField</code> — punya parameter <code class="language-plaintext highlighter-rouge">validator</code> yang nggak dimiliki <code class="language-plaintext highlighter-rouge">TextField</code> biasa</li>
  <li><code class="language-plaintext highlighter-rouge">validator</code> — return <code class="language-plaintext highlighter-rouge">null</code> kalau valid, return <code class="language-plaintext highlighter-rouge">String</code> error kalau invalid</li>
  <li><code class="language-plaintext highlighter-rouge">.validate()</code> — memanggil semua validator, return <code class="language-plaintext highlighter-rouge">true</code> hanya jika SEMUA field valid</li>
</ol>

<hr />

<h2 id="3-textfield-vs-textformfield--kapan-pakai">3. TextField vs TextFormField — Kapan Pakai?</h2>

<table>
  <thead>
    <tr>
      <th>Widget</th>
      <th>Best For</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">TextField</code></td>
      <td>Input tunggal, search bar, chat input, field yang nggak perlu validasi</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">TextFormField</code></td>
      <td>Di dalam <code class="language-plaintext highlighter-rouge">Form</code>, butuh <code class="language-plaintext highlighter-rouge">validator</code>, butuh validasi kolektif</td>
    </tr>
  </tbody>
</table>

<p><strong>Pro tip:</strong> Kalau kamu cuma punya satu field (misal: search bar), pakai <code class="language-plaintext highlighter-rouge">TextField</code> — lebih ringan. Kalau lebih dari satu field dan ada logic validasi (register, login, checkout), pakai <code class="language-plaintext highlighter-rouge">Form</code> + <code class="language-plaintext highlighter-rouge">TextFormField</code> — lebih rapi dan terstruktur.</p>

<hr />

<h2 id="4-pola-nyata-form-auto-save-ke-state">4. Pola Nyata: Form Auto-Save ke State</h2>

<p>Kadang kamu nggak langsung submit ke server. Kamu cuma simpan dulu ke state lokal:</p>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Di dalam State class</span>
<span class="kt">String</span> <span class="n">_nama</span> <span class="o">=</span> <span class="s">''</span><span class="p">;</span>
<span class="kt">String</span> <span class="n">_email</span> <span class="o">=</span> <span class="s">''</span><span class="p">;</span>
<span class="kt">String</span> <span class="n">_password</span> <span class="o">=</span> <span class="s">''</span><span class="p">;</span>

<span class="c1">// Simpan data sebelum validasi</span>
<span class="n">_formKey</span><span class="o">.</span><span class="na">currentState</span><span class="o">!.</span><span class="na">save</span><span class="p">();</span>  <span class="c1">// ⬅️ trigger semua onSaved callback</span>

<span class="c1">// Setelah save, semua variabel di atas terisi</span>
<span class="n">print</span><span class="p">(</span><span class="s">'Data tersimpan: </span><span class="si">$_nama</span><span class="s">, </span><span class="si">$_email</span><span class="s">'</span><span class="p">);</span>
</code></pre></div></div>

<p>Caranya: tiap <code class="language-plaintext highlighter-rouge">TextFormField</code> kasih callback <code class="language-plaintext highlighter-rouge">onSaved</code>:</p>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">TextFormField</span><span class="p">(</span>
  <span class="nl">decoration:</span> <span class="kd">const</span> <span class="n">InputDecoration</span><span class="p">(</span><span class="nl">labelText:</span> <span class="s">'Nama Lengkap'</span><span class="p">),</span>
  <span class="nl">validator:</span> <span class="p">(</span><span class="n">v</span><span class="p">)</span> <span class="o">=</span><span class="p">&gt;</span> <span class="n">v</span><span class="o">!.</span><span class="na">isEmpty</span> <span class="o">?</span> <span class="s">'Wajib diisi'</span> <span class="o">:</span> <span class="kc">null</span><span class="p">,</span>
  <span class="nl">onSaved:</span> <span class="p">(</span><span class="n">v</span><span class="p">)</span> <span class="o">=</span><span class="p">&gt;</span> <span class="n">_nama</span> <span class="o">=</span> <span class="n">v</span> <span class="o">??</span> <span class="s">''</span><span class="p">,</span>   <span class="c1">// ← di sini!</span>
<span class="p">)</span>
</code></pre></div></div>

<p>Pola <code class="language-plaintext highlighter-rouge">save()</code> + <code class="language-plaintext highlighter-rouge">validate()</code> ini lazim di production app.</p>

<hr />

<p>Hari ini kamu udah bisa menerima input user dengan dua cara: <strong>TextField</strong> buat simpel, <strong>Form + TextFormField</strong> buat yang butuh validasi terstruktur. Ini adalah fondasi penting karena <strong>hampir semua aplikasi nyata butuh form</strong>.</p>

<p>Besok kita akan lanjut ke <strong>Validasi Form &amp; SnackBar Feedback</strong> — bagaimana kasih pesan error yang jelas dan user-friendly. Stay tuned! 🚀</p>

<p><strong>Coba sendiri! Bikin form login 2 field (email + password) dengan validasi. Share hasilnya ke sosial media dan tag @ahsai001!</strong></p>]]></content><author><name></name></author><category term="flutter" /><category term="dart" /><category term="form" /><category term="textfield" /><category term="input" /><category term="tutorial" /><category term="indonesia" /><category term="pemrograman" /><summary type="html"><![CDATA[Aplikasi yang cuma bisa tampil doang itu kayak TV tanpa remote — cuma bisa dilihat, nggak bisa diajak interaksi. Realitasnya, hampir semua aplikasi butuh input dari user: login, register, search, chat, notes, form order, dll. Di Flutter, senjata utamanya ada dua: TextField (input satuan) dan Form (kumpulan input dengan validasi).]]></summary></entry><entry><title type="html">Flutter #8: Navigasi — Navigator.push &amp;amp; Named Routes</title><link href="https://ahsai.my.id/blog/2026/08/10/flutter-8-navigasi-navigator-push-named-routes/" rel="alternate" type="text/html" title="Flutter #8: Navigasi — Navigator.push &amp;amp; Named Routes" /><published>2026-08-10T00:00:00+00:00</published><updated>2026-08-10T00:00:00+00:00</updated><id>https://ahsai.my.id/blog/2026/08/10/flutter-8-navigasi-navigator-push-named-routes</id><content type="html" xml:base="https://ahsai.my.id/blog/2026/08/10/flutter-8-navigasi-navigator-push-named-routes/"><![CDATA[<p>Aplikasi yang cuma satu layar itu namanya bukan aplikasi — itu cuma gambar. Aplikasi beneran pasti punya beberapa halaman: dari halaman login ke dashboard, dari list produk ke detail, dari keranjang ke checkout. Nah, di Flutter, perpindahan antar halaman ini disebut <strong>Navigasi</strong>, dan senjatanya adalah class <code class="language-plaintext highlighter-rouge">Navigator</code>.</p>

<p>Hari ini kita bahas dua metode navigasi yang paling sering dipakai: <strong>Navigator.push</strong> (imperatif) dan <strong>Named Routes</strong> (deklaratif). Dua-duanya wajib kamu kuasai!</p>

<hr />

<h2 id="1-konsep-dasar-stack-navigation">1. Konsep Dasar: Stack Navigation</h2>

<p>Flutter menggunakan konsep <strong>stack</strong> — ingat tumpukan piring. Setiap kamu “push” halaman baru, dia numpuk di atas halaman sebelumnya. Setiap “pop”, halaman paling atas dibuang dan kamu kembali ke halaman di bawahnya.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>┌─────────────┐
│  Detail     │ ← push (tambah)
├─────────────┤
│  List Menu  │ ← halaman sebelumnya
├─────────────┤
│  Home       │ ← halaman awal
└─────────────┘
</code></pre></div></div>

<p>Sederhana, tapi powerful. Tanpa perlu bikin logic ribet, Flutter udah handle animasi transisi dan back-button Android secara otomatis.</p>

<hr />

<h2 id="2-navigatorpush--cara-paling-simpel">2. Navigator.push — Cara Paling Simpel</h2>

<p><code class="language-plaintext highlighter-rouge">Navigator.push</code> menerima dua parameter: <code class="language-plaintext highlighter-rouge">context</code> dan <code class="language-plaintext highlighter-rouge">MaterialPageRoute</code>. Ini metode paling cocok buat pemula karena eksplisit.</p>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="s">'package:flutter/material.dart'</span><span class="o">;</span>

<span class="kt">void</span> <span class="nf">main</span><span class="p">()</span> <span class="o">=</span><span class="p">&gt;</span> <span class="n">runApp</span><span class="p">(</span><span class="kd">const</span> <span class="n">MaterialApp</span><span class="p">(</span><span class="nl">home:</span> <span class="n">HomeScreen</span><span class="p">()));</span>

<span class="c1">// ========== HALAMAN PERTAMA ==========</span>
<span class="kd">class</span> <span class="nc">HomeScreen</span> <span class="kd">extends</span> <span class="n">StatelessWidget</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="n">HomeScreen</span><span class="p">({</span><span class="k">super</span><span class="o">.</span><span class="na">key</span><span class="p">});</span>

  <span class="nd">@override</span>
  <span class="n">Widget</span> <span class="n">build</span><span class="p">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span> <span class="n">Scaffold</span><span class="p">(</span>
      <span class="nl">appBar:</span> <span class="n">AppBar</span><span class="p">(</span><span class="nl">title:</span> <span class="kd">const</span> <span class="n">Text</span><span class="p">(</span><span class="s">'🏠 Halaman Utama'</span><span class="p">)),</span>
      <span class="nl">body:</span> <span class="n">Center</span><span class="p">(</span>
        <span class="nl">child:</span> <span class="n">ElevatedButton</span><span class="p">(</span>
          <span class="nl">onPressed:</span> <span class="p">()</span> <span class="p">{</span>
            <span class="c1">// 🌟 NAVIGASI KE HALAMAN KEDUA</span>
            <span class="n">Navigator</span><span class="o">.</span><span class="na">push</span><span class="p">(</span>
              <span class="n">context</span><span class="p">,</span>
              <span class="n">MaterialPageRoute</span><span class="p">(</span><span class="nl">builder:</span> <span class="p">(</span><span class="n">context</span><span class="p">)</span> <span class="o">=</span><span class="p">&gt;</span> <span class="kd">const</span> <span class="n">DetailScreen</span><span class="p">()),</span>
            <span class="p">);</span>
          <span class="p">},</span>
          <span class="nl">child:</span> <span class="kd">const</span> <span class="n">Text</span><span class="p">(</span><span class="s">'Lihat Detail ➡️'</span><span class="p">),</span>
        <span class="p">),</span>
      <span class="p">),</span>
    <span class="p">);</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="c1">// ========== HALAMAN KEDUA ==========</span>
<span class="kd">class</span> <span class="nc">DetailScreen</span> <span class="kd">extends</span> <span class="n">StatelessWidget</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="n">DetailScreen</span><span class="p">({</span><span class="k">super</span><span class="o">.</span><span class="na">key</span><span class="p">});</span>

  <span class="nd">@override</span>
  <span class="n">Widget</span> <span class="n">build</span><span class="p">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span> <span class="n">Scaffold</span><span class="p">(</span>
      <span class="nl">appBar:</span> <span class="n">AppBar</span><span class="p">(</span><span class="nl">title:</span> <span class="kd">const</span> <span class="n">Text</span><span class="p">(</span><span class="s">'📄 Halaman Detail'</span><span class="p">)),</span>
      <span class="nl">body:</span> <span class="n">Center</span><span class="p">(</span>
        <span class="nl">child:</span> <span class="n">ElevatedButton</span><span class="p">(</span>
          <span class="nl">onPressed:</span> <span class="p">()</span> <span class="p">{</span>
            <span class="c1">// ⬅️ KEMBALI KE HALAMAN SEBELUMNYA</span>
            <span class="n">Navigator</span><span class="o">.</span><span class="na">pop</span><span class="p">(</span><span class="n">context</span><span class="p">);</span>
          <span class="p">},</span>
          <span class="nl">child:</span> <span class="kd">const</span> <span class="n">Text</span><span class="p">(</span><span class="s">'⬅️ Kembali'</span><span class="p">),</span>
        <span class="p">),</span>
      <span class="p">),</span>
    <span class="p">);</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>Penjelasan:</strong></p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">Navigator.push(...)</code> → dorong halaman baru ke atas stack</li>
  <li><code class="language-plaintext highlighter-rouge">MaterialPageRoute</code> → animasi transisi sesuai platform (slide dari kanan di Android, fade di iOS)</li>
  <li><code class="language-plaintext highlighter-rouge">Navigator.pop(context)</code> → buang halaman saat ini dari stack, kembali ke sebelumnya</li>
</ul>

<h3 id="pass-data-antar-halaman">Pass Data Antar Halaman</h3>

<p>Kirim data dari Home ke Detail? Gampang. Karena <code class="language-plaintext highlighter-rouge">DetailScreen</code> adalah constructor biasa:</p>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Kirim dari HomeScreen</span>
<span class="n">Navigator</span><span class="o">.</span><span class="na">push</span><span class="p">(</span>
  <span class="n">context</span><span class="p">,</span>
  <span class="n">MaterialPageRoute</span><span class="p">(</span>
    <span class="nl">builder:</span> <span class="p">(</span><span class="n">context</span><span class="p">)</span> <span class="o">=</span><span class="p">&gt;</span> <span class="n">DetailScreen</span><span class="p">(</span><span class="nl">nama:</span> <span class="s">'Ahmad'</span><span class="p">,</span> <span class="nl">umur:</span> <span class="mi">25</span><span class="p">),</span>
  <span class="p">),</span>
<span class="p">);</span>

<span class="c1">// DetailScreen terima lewat constructor</span>
<span class="kd">class</span> <span class="nc">DetailScreen</span> <span class="kd">extends</span> <span class="n">StatelessWidget</span> <span class="p">{</span>
  <span class="kd">final</span> <span class="kt">String</span> <span class="n">nama</span><span class="p">;</span>
  <span class="kd">final</span> <span class="kt">int</span> <span class="n">umur</span><span class="p">;</span>

  <span class="kd">const</span> <span class="n">DetailScreen</span><span class="p">({</span><span class="k">super</span><span class="o">.</span><span class="na">key</span><span class="p">,</span> <span class="kd">required</span> <span class="k">this</span><span class="o">.</span><span class="na">nama</span><span class="p">,</span> <span class="kd">required</span> <span class="k">this</span><span class="o">.</span><span class="na">umur</span><span class="p">});</span>

  <span class="nd">@override</span>
  <span class="n">Widget</span> <span class="n">build</span><span class="p">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span> <span class="n">Scaffold</span><span class="p">(</span>
      <span class="nl">appBar:</span> <span class="n">AppBar</span><span class="p">(</span><span class="nl">title:</span> <span class="n">Text</span><span class="p">(</span><span class="s">'Halo, </span><span class="si">$nama</span><span class="s">!'</span><span class="p">)),</span>
      <span class="nl">body:</span> <span class="n">Center</span><span class="p">(</span><span class="nl">child:</span> <span class="n">Text</span><span class="p">(</span><span class="s">'Umur: </span><span class="si">$umur</span><span class="s"> tahun'</span><span class="p">)),</span>
    <span class="p">);</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Dan kalau mau <strong>balikin data</strong> dari DetailScreen ke HomeScreen:</p>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Di DetailScreen, pop sambil bawa data</span>
<span class="n">Navigator</span><span class="o">.</span><span class="na">pop</span><span class="p">(</span><span class="n">context</span><span class="p">,</span> <span class="s">'Data dari Detail'</span><span class="p">);</span>

<span class="c1">// Di HomeScreen, tangkap hasilnya</span>
<span class="kd">final</span> <span class="n">hasil</span> <span class="o">=</span> <span class="k">await</span> <span class="n">Navigator</span><span class="o">.</span><span class="na">push</span><span class="p">(</span>
  <span class="n">context</span><span class="p">,</span>
  <span class="n">MaterialPageRoute</span><span class="p">(</span><span class="nl">builder:</span> <span class="p">(</span><span class="n">context</span><span class="p">)</span> <span class="o">=</span><span class="p">&gt;</span> <span class="kd">const</span> <span class="n">DetailScreen</span><span class="p">()),</span>
<span class="p">);</span>
<span class="n">print</span><span class="p">(</span><span class="s">'Hasil: </span><span class="si">$hasil</span><span class="s">'</span><span class="p">);</span> <span class="c1">// Output: Hasil: Data dari Detail</span>
</code></pre></div></div>

<hr />

<h2 id="3-named-routes--navigasi-deklaratif">3. Named Routes — Navigasi Deklaratif</h2>

<p>Kalau aplikasi udah punya banyak halaman, <code class="language-plaintext highlighter-rouge">MaterialPageRoute</code> satu-satu mulai ribet. Solusinya <strong>named routes</strong> — daftarin semua route di awal, panggil tinggal sebut nama.</p>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="s">'package:flutter/material.dart'</span><span class="o">;</span>

<span class="kt">void</span> <span class="nf">main</span><span class="p">()</span> <span class="o">=</span><span class="p">&gt;</span> <span class="n">runApp</span><span class="p">(</span><span class="kd">const</span> <span class="n">MyApp</span><span class="p">());</span>

<span class="kd">class</span> <span class="nc">MyApp</span> <span class="kd">extends</span> <span class="n">StatelessWidget</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="n">MyApp</span><span class="p">({</span><span class="k">super</span><span class="o">.</span><span class="na">key</span><span class="p">});</span>

  <span class="nd">@override</span>
  <span class="n">Widget</span> <span class="n">build</span><span class="p">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span> <span class="n">MaterialApp</span><span class="p">(</span>
      <span class="nl">title:</span> <span class="s">'Flutter Navigation Demo'</span><span class="p">,</span>
      <span class="nl">initialRoute:</span> <span class="s">'/'</span><span class="p">,</span>                        <span class="c1">// halaman default</span>
      <span class="nl">routes:</span> <span class="p">{</span>
        <span class="s">'/'</span><span class="o">:</span> <span class="p">(</span><span class="n">context</span><span class="p">)</span> <span class="o">=</span><span class="p">&gt;</span> <span class="kd">const</span> <span class="n">HomeScreen</span><span class="p">(),</span>   <span class="c1">// ← wajib ada initialRoute</span>
        <span class="s">'/detail'</span><span class="o">:</span> <span class="p">(</span><span class="n">context</span><span class="p">)</span> <span class="o">=</span><span class="p">&gt;</span> <span class="kd">const</span> <span class="n">DetailScreen</span><span class="p">(),</span>
        <span class="s">'/profile'</span><span class="o">:</span> <span class="p">(</span><span class="n">context</span><span class="p">)</span> <span class="o">=</span><span class="p">&gt;</span> <span class="kd">const</span> <span class="n">ProfileScreen</span><span class="p">(),</span>
      <span class="p">},</span>
    <span class="p">);</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="kd">class</span> <span class="nc">HomeScreen</span> <span class="kd">extends</span> <span class="n">StatelessWidget</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="n">HomeScreen</span><span class="p">({</span><span class="k">super</span><span class="o">.</span><span class="na">key</span><span class="p">});</span>

  <span class="nd">@override</span>
  <span class="n">Widget</span> <span class="n">build</span><span class="p">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span> <span class="n">Scaffold</span><span class="p">(</span>
      <span class="nl">appBar:</span> <span class="n">AppBar</span><span class="p">(</span><span class="nl">title:</span> <span class="kd">const</span> <span class="n">Text</span><span class="p">(</span><span class="s">'🏠 Home'</span><span class="p">)),</span>
      <span class="nl">body:</span> <span class="n">Center</span><span class="p">(</span>
        <span class="nl">child:</span> <span class="n">Column</span><span class="p">(</span>
          <span class="nl">mainAxisAlignment:</span> <span class="n">MainAxisAlignment</span><span class="o">.</span><span class="na">center</span><span class="p">,</span>
          <span class="nl">children:</span> <span class="p">[</span>
            <span class="n">ElevatedButton</span><span class="p">(</span>
              <span class="nl">onPressed:</span> <span class="p">()</span> <span class="p">{</span>
                <span class="n">Navigator</span><span class="o">.</span><span class="na">pushNamed</span><span class="p">(</span><span class="n">context</span><span class="p">,</span> <span class="s">'/detail'</span><span class="p">);</span>  <span class="c1">// ← by name!</span>
              <span class="p">},</span>
              <span class="nl">child:</span> <span class="kd">const</span> <span class="n">Text</span><span class="p">(</span><span class="s">'Ke Detail ➡️'</span><span class="p">),</span>
            <span class="p">),</span>
            <span class="kd">const</span> <span class="n">SizedBox</span><span class="p">(</span><span class="nl">height:</span> <span class="mi">12</span><span class="p">),</span>
            <span class="n">ElevatedButton</span><span class="p">(</span>
              <span class="nl">onPressed:</span> <span class="p">()</span> <span class="p">{</span>
                <span class="n">Navigator</span><span class="o">.</span><span class="na">pushNamed</span><span class="p">(</span><span class="n">context</span><span class="p">,</span> <span class="s">'/profile'</span><span class="p">);</span>
              <span class="p">},</span>
              <span class="nl">child:</span> <span class="kd">const</span> <span class="n">Text</span><span class="p">(</span><span class="s">'Ke Profil 👤'</span><span class="p">),</span>
            <span class="p">),</span>
          <span class="p">],</span>
        <span class="p">),</span>
      <span class="p">),</span>
    <span class="p">);</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="kd">class</span> <span class="nc">DetailScreen</span> <span class="kd">extends</span> <span class="n">StatelessWidget</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="n">DetailScreen</span><span class="p">({</span><span class="k">super</span><span class="o">.</span><span class="na">key</span><span class="p">});</span>
  <span class="nd">@override</span>
  <span class="n">Widget</span> <span class="n">build</span><span class="p">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="p">)</span> <span class="o">=</span><span class="p">&gt;</span> <span class="n">Scaffold</span><span class="p">(</span>
    <span class="nl">appBar:</span> <span class="n">AppBar</span><span class="p">(</span><span class="nl">title:</span> <span class="kd">const</span> <span class="n">Text</span><span class="p">(</span><span class="s">'📄 Detail'</span><span class="p">)),</span>
    <span class="nl">body:</span> <span class="n">Center</span><span class="p">(</span><span class="nl">child:</span> <span class="n">Text</span><span class="p">(</span><span class="s">'Halaman Detail'</span><span class="p">)),</span>
  <span class="p">);</span>
<span class="p">}</span>

<span class="kd">class</span> <span class="nc">ProfileScreen</span> <span class="kd">extends</span> <span class="n">StatelessWidget</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="n">ProfileScreen</span><span class="p">({</span><span class="k">super</span><span class="o">.</span><span class="na">key</span><span class="p">});</span>
  <span class="nd">@override</span>
  <span class="n">Widget</span> <span class="n">build</span><span class="p">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="p">)</span> <span class="o">=</span><span class="p">&gt;</span> <span class="n">Scaffold</span><span class="p">(</span>
    <span class="nl">appBar:</span> <span class="n">AppBar</span><span class="p">(</span><span class="nl">title:</span> <span class="kd">const</span> <span class="n">Text</span><span class="p">(</span><span class="s">'👤 Profil'</span><span class="p">)),</span>
    <span class="nl">body:</span> <span class="n">Center</span><span class="p">(</span><span class="nl">child:</span> <span class="n">Text</span><span class="p">(</span><span class="s">'Halaman Profil'</span><span class="p">)),</span>
  <span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>Kelebihan Named Routes:</strong></p>
<ul>
  <li>Semua route kelihatan di satu tempat — gampang maintenancenya</li>
  <li>Cocok untuk aplikasi menengah-besar</li>
  <li>Bisa pakai <code class="language-plaintext highlighter-rouge">onGenerateRoute</code> untuk dynamic routing (ada parameter, guard auth, dsb)</li>
</ul>

<h3 id="pass-arguments-dengan-named-routes">Pass Arguments dengan Named Routes</h3>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Kirim argument</span>
<span class="n">Navigator</span><span class="o">.</span><span class="na">pushNamed</span><span class="p">(</span>
  <span class="n">context</span><span class="p">,</span>
  <span class="s">'/detail'</span><span class="p">,</span>
  <span class="nl">arguments:</span> <span class="p">{</span><span class="s">'nama'</span><span class="o">:</span> <span class="s">'Siti'</span><span class="p">,</span> <span class="s">'role'</span><span class="o">:</span> <span class="s">'admin'</span><span class="p">},</span>
<span class="p">);</span>

<span class="c1">// Terima di halaman tujuan</span>
<span class="kd">class</span> <span class="nc">DetailScreen</span> <span class="kd">extends</span> <span class="n">StatelessWidget</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="n">DetailScreen</span><span class="p">({</span><span class="k">super</span><span class="o">.</span><span class="na">key</span><span class="p">});</span>

  <span class="nd">@override</span>
  <span class="n">Widget</span> <span class="n">build</span><span class="p">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">final</span> <span class="n">args</span> <span class="o">=</span> <span class="n">ModalRoute</span><span class="o">.</span><span class="na">of</span><span class="p">(</span><span class="n">context</span><span class="p">)</span><span class="o">!.</span><span class="na">settings</span><span class="o">.</span><span class="na">arguments</span> <span class="k">as</span> <span class="kt">Map</span><span class="p">&lt;</span><span class="kt">String</span><span class="p">,</span> <span class="kt">String</span><span class="p">&gt;;</span>
    <span class="k">return</span> <span class="n">Scaffold</span><span class="p">(</span>
      <span class="nl">appBar:</span> <span class="n">AppBar</span><span class="p">(</span><span class="nl">title:</span> <span class="n">Text</span><span class="p">(</span><span class="s">'Halo, </span><span class="si">${args['nama']}</span><span class="s">!'</span><span class="p">)),</span>
      <span class="nl">body:</span> <span class="n">Center</span><span class="p">(</span><span class="nl">child:</span> <span class="n">Text</span><span class="p">(</span><span class="s">'Role: </span><span class="si">${args['role']}</span><span class="s">'</span><span class="p">)),</span>
    <span class="p">);</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<hr />

<h2 id="4-catatan-penting-navigasi">4. Catatan Penting Navigasi</h2>

<table>
  <thead>
    <tr>
      <th>Metode</th>
      <th>Kapan Dipakai</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">Navigator.push</code> + <code class="language-plaintext highlighter-rouge">MaterialPageRoute</code></td>
      <td>Aplikasi kecil (1-5 halaman), butuh pass data sederhana</td>
    </tr>
    <tr>
      <td>Named Routes (<code class="language-plaintext highlighter-rouge">routes: {...}</code>)</td>
      <td>Aplikasi menengah, banyak halaman static</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">onGenerateRoute</code></td>
      <td>Dynamic routing, butuh auth guard, parsing URL parameter</td>
    </tr>
  </tbody>
</table>

<p><strong>Pro tip:</strong> Jangan kirim objek yang kompleks lewat arguments — mending kirim ID, lalu fetch datanya di halaman tujuan. Lebih bersih dan lebih aman.</p>

<hr />

<h2 id="5-push-vs-pushnamed-beda-tipis-tapi-penting">5. Push vs PushNamed: Beda Tipis Tapi Penting</h2>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// IMPERATIF — kamu yang atur route-nya manual</span>
<span class="n">Navigator</span><span class="o">.</span><span class="na">push</span><span class="p">(</span><span class="n">context</span><span class="p">,</span> <span class="n">MaterialPageRoute</span><span class="p">(</span><span class="nl">builder:</span> <span class="p">(</span><span class="n">_</span><span class="p">)</span> <span class="o">=</span><span class="p">&gt;</span> <span class="n">TargetScreen</span><span class="p">()));</span>

<span class="c1">// DEKLARATIF — tinggal sebut nama, Flutter yang handle</span>
<span class="n">Navigator</span><span class="o">.</span><span class="na">pushNamed</span><span class="p">(</span><span class="n">context</span><span class="p">,</span> <span class="s">'/target'</span><span class="p">);</span>
</code></pre></div></div>

<p>Keduanya valid. Pilih yang bikin kode kamu lebih rapi dan mudah dibaca. Di proyek real, tim biasanya pakai named routes + <code class="language-plaintext highlighter-rouge">onGenerateRoute</code> biar scalable.</p>

<hr />

<p>Hari ini kamu udah belajar cara berpindah halaman — skill fundamental yang <strong>ada di 100% aplikasi Flutter</strong>. Dari dua layar sederhana sampai puluhan halaman dengan auth guard, semuanya berawal dari <code class="language-plaintext highlighter-rouge">Navigator.push</code> dan <code class="language-plaintext highlighter-rouge">pushNamed</code>.</p>

<p>Besok kita lanjut ke <strong>Form &amp; TextField</strong> — bagaimana menerima input dari user dengan benar. Stay tuned! 🚀</p>

<p><strong>Coba sendiri! Bikin aplikasi 3 halaman dengan named routes. Share hasilnya ke sosial media dan tag @ahsai001!</strong></p>]]></content><author><name></name></author><category term="flutter" /><category term="dart" /><category term="navigasi" /><category term="navigator" /><category term="routes" /><category term="tutorial" /><category term="indonesia" /><category term="pemrograman" /><summary type="html"><![CDATA[Aplikasi yang cuma satu layar itu namanya bukan aplikasi — itu cuma gambar. Aplikasi beneran pasti punya beberapa halaman: dari halaman login ke dashboard, dari list produk ke detail, dari keranjang ke checkout. Nah, di Flutter, perpindahan antar halaman ini disebut Navigasi, dan senjatanya adalah class Navigator.]]></summary></entry><entry><title type="html">Flutter #7: ListView &amp;amp; GridView — Bikin List Konten</title><link href="https://ahsai.my.id/blog/2026/08/09/flutter-7-listview-gridview-bikin-list-konten/" rel="alternate" type="text/html" title="Flutter #7: ListView &amp;amp; GridView — Bikin List Konten" /><published>2026-08-09T00:00:00+00:00</published><updated>2026-08-09T00:00:00+00:00</updated><id>https://ahsai.my.id/blog/2026/08/09/flutter-7-listview-gridview-bikin-list-konten</id><content type="html" xml:base="https://ahsai.my.id/blog/2026/08/09/flutter-7-listview-gridview-bikin-list-konten/"><![CDATA[<p>Aplikasi yang cuma satu layar statis itu ibarat warung kosong — sepi, gak ada yang bisa di-scroll. Nah, dua widget paling penting untuk bikin konten yang bisa digulir adalah <strong>ListView</strong> dan <strong>GridView</strong>. Mau bikin feed berita, daftar kontak, galeri foto, sampai katalog produk? Dua inilah senjata utamamu.</p>

<p>Hari ini kita akan bahas dari yang paling sederhana sampai yang performanya juara. Let’s go!</p>

<hr />

<h2 id="1-listview--raja-daftar-vertikal">1. ListView — Raja Daftar Vertikal</h2>

<p><code class="language-plaintext highlighter-rouge">ListView</code> adalah widget untuk menampilkan daftar item yang bisa di-scroll secara vertikal (atau horizontal). Cara paling simpel:</p>

<h3 id="listview-sederhana">ListView Sederhana</h3>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="s">'package:flutter/material.dart'</span><span class="o">;</span>

<span class="kt">void</span> <span class="nf">main</span><span class="p">()</span> <span class="o">=</span><span class="p">&gt;</span> <span class="n">runApp</span><span class="p">(</span><span class="kd">const</span> <span class="n">MaterialApp</span><span class="p">(</span><span class="nl">home:</span> <span class="n">ListBasicScreen</span><span class="p">()));</span>

<span class="kd">class</span> <span class="nc">ListBasicScreen</span> <span class="kd">extends</span> <span class="n">StatelessWidget</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="n">ListBasicScreen</span><span class="p">({</span><span class="k">super</span><span class="o">.</span><span class="na">key</span><span class="p">});</span>

  <span class="nd">@override</span>
  <span class="n">Widget</span> <span class="n">build</span><span class="p">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span> <span class="n">Scaffold</span><span class="p">(</span>
      <span class="nl">appBar:</span> <span class="n">AppBar</span><span class="p">(</span><span class="nl">title:</span> <span class="kd">const</span> <span class="n">Text</span><span class="p">(</span><span class="s">'ListView Dasar'</span><span class="p">)),</span>
      <span class="nl">body:</span> <span class="n">ListView</span><span class="p">(</span>
        <span class="nl">children:</span> <span class="kd">const</span> <span class="p">[</span>
          <span class="n">ListTile</span><span class="p">(</span>
            <span class="nl">leading:</span> <span class="n">Icon</span><span class="p">(</span><span class="n">Icons</span><span class="o">.</span><span class="na">person</span><span class="p">),</span>
            <span class="nl">title:</span> <span class="n">Text</span><span class="p">(</span><span class="s">'Budi Santoso'</span><span class="p">),</span>
            <span class="nl">subtitle:</span> <span class="n">Text</span><span class="p">(</span><span class="s">'Flutter Developer'</span><span class="p">),</span>
          <span class="p">),</span>
          <span class="n">ListTile</span><span class="p">(</span>
            <span class="nl">leading:</span> <span class="n">Icon</span><span class="p">(</span><span class="n">Icons</span><span class="o">.</span><span class="na">person</span><span class="p">),</span>
            <span class="nl">title:</span> <span class="n">Text</span><span class="p">(</span><span class="s">'Siti Aminah'</span><span class="p">),</span>
            <span class="nl">subtitle:</span> <span class="n">Text</span><span class="p">(</span><span class="s">'UI/UX Designer'</span><span class="p">),</span>
          <span class="p">),</span>
          <span class="n">ListTile</span><span class="p">(</span>
            <span class="nl">leading:</span> <span class="n">Icon</span><span class="p">(</span><span class="n">Icons</span><span class="o">.</span><span class="na">person</span><span class="p">),</span>
            <span class="nl">title:</span> <span class="n">Text</span><span class="p">(</span><span class="s">'Joko Widodo'</span><span class="p">),</span>
            <span class="nl">subtitle:</span> <span class="n">Text</span><span class="p">(</span><span class="s">'Project Manager'</span><span class="p">),</span>
          <span class="p">),</span>
        <span class="p">],</span>
      <span class="p">),</span>
    <span class="p">);</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Cocok buat list yang <strong>pendek dan jumlahnya fix</strong>. Tapi kalau datanya ratusan? Jangan! Kenapa? Karena <code class="language-plaintext highlighter-rouge">ListView(children: [...])</code> langsung me-render <em>semua</em> child widget sekaligus — memory boros, performa jeblok.</p>

<hr />

<h2 id="2-listviewbuilder--pahlawan-performa">2. ListView.builder — Pahlawan Performa</h2>

<p>Untuk data banyak (dari API, database, atau list panjang), pakai <code class="language-plaintext highlighter-rouge">ListView.builder</code>. Dia cuma me-render item yang <strong>terlihat di layar</strong> — mirip RecyclerView di Android.</p>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="s">'package:flutter/material.dart'</span><span class="o">;</span>

<span class="kt">void</span> <span class="nf">main</span><span class="p">()</span> <span class="o">=</span><span class="p">&gt;</span> <span class="n">runApp</span><span class="p">(</span><span class="kd">const</span> <span class="n">MaterialApp</span><span class="p">(</span><span class="nl">home:</span> <span class="n">ListBuilderScreen</span><span class="p">()));</span>

<span class="kd">class</span> <span class="nc">ListBuilderScreen</span> <span class="kd">extends</span> <span class="n">StatelessWidget</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="n">ListBuilderScreen</span><span class="p">({</span><span class="k">super</span><span class="o">.</span><span class="na">key</span><span class="p">});</span>

  <span class="c1">// Simulasi data dari API</span>
  <span class="kd">final</span> <span class="kt">List</span><span class="p">&lt;</span><span class="kt">Map</span><span class="p">&lt;</span><span class="kt">String</span><span class="p">,</span> <span class="kt">String</span><span class="p">&gt;&gt;</span> <span class="n">_contacts</span> <span class="o">=</span> <span class="kd">const</span> <span class="p">[</span>
    <span class="p">{</span><span class="s">'name'</span><span class="o">:</span> <span class="s">'Ahmad Fathan'</span><span class="p">,</span> <span class="s">'role'</span><span class="o">:</span> <span class="s">'Senior Flutter Dev'</span><span class="p">},</span>
    <span class="p">{</span><span class="s">'name'</span><span class="o">:</span> <span class="s">'Rina Marlina'</span><span class="p">,</span> <span class="s">'role'</span><span class="o">:</span> <span class="s">'Backend Engineer'</span><span class="p">},</span>
    <span class="p">{</span><span class="s">'name'</span><span class="o">:</span> <span class="s">'Doni Prasetyo'</span><span class="p">,</span> <span class="s">'role'</span><span class="o">:</span> <span class="s">'DevOps'</span><span class="p">},</span>
    <span class="p">{</span><span class="s">'name'</span><span class="o">:</span> <span class="s">'Putri Ayu'</span><span class="p">,</span> <span class="s">'role'</span><span class="o">:</span> <span class="s">'Product Manager'</span><span class="p">},</span>
    <span class="p">{</span><span class="s">'name'</span><span class="o">:</span> <span class="s">'Eko Nugroho'</span><span class="p">,</span> <span class="s">'role'</span><span class="o">:</span> <span class="s">'Mobile Developer'</span><span class="p">},</span>
    <span class="p">{</span><span class="s">'name'</span><span class="o">:</span> <span class="s">'Fitri Handayani'</span><span class="p">,</span> <span class="s">'role'</span><span class="o">:</span> <span class="s">'QA Engineer'</span><span class="p">},</span>
    <span class="p">{</span><span class="s">'name'</span><span class="o">:</span> <span class="s">'Bayu Saputra'</span><span class="p">,</span> <span class="s">'role'</span><span class="o">:</span> <span class="s">'Data Scientist'</span><span class="p">},</span>
    <span class="p">{</span><span class="s">'name'</span><span class="o">:</span> <span class="s">'Citra Lestari'</span><span class="p">,</span> <span class="s">'role'</span><span class="o">:</span> <span class="s">'Tech Lead'</span><span class="p">},</span>
    <span class="p">{</span><span class="s">'name'</span><span class="o">:</span> <span class="s">'Galih Prakoso'</span><span class="p">,</span> <span class="s">'role'</span><span class="o">:</span> <span class="s">'Frontend Dev'</span><span class="p">},</span>
    <span class="p">{</span><span class="s">'name'</span><span class="o">:</span> <span class="s">'Indah Permatasari'</span><span class="p">,</span> <span class="s">'role'</span><span class="o">:</span> <span class="s">'Scrum Master'</span><span class="p">},</span>
  <span class="p">];</span>

  <span class="nd">@override</span>
  <span class="n">Widget</span> <span class="n">build</span><span class="p">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span> <span class="n">Scaffold</span><span class="p">(</span>
      <span class="nl">appBar:</span> <span class="n">AppBar</span><span class="p">(</span>
        <span class="nl">title:</span> <span class="kd">const</span> <span class="n">Text</span><span class="p">(</span><span class="s">'ListView.builder'</span><span class="p">),</span>
        <span class="nl">centerTitle:</span> <span class="kc">true</span><span class="p">,</span>
      <span class="p">),</span>
      <span class="nl">body:</span> <span class="n">ListView</span><span class="o">.</span><span class="na">builder</span><span class="p">(</span>
        <span class="nl">itemCount:</span> <span class="n">_contacts</span><span class="o">.</span><span class="na">length</span><span class="p">,</span>
        <span class="nl">itemBuilder:</span> <span class="p">(</span><span class="n">context</span><span class="p">,</span> <span class="n">index</span><span class="p">)</span> <span class="p">{</span>
          <span class="kd">final</span> <span class="n">contact</span> <span class="o">=</span> <span class="n">_contacts</span><span class="p">[</span><span class="n">index</span><span class="p">];</span>
          <span class="k">return</span> <span class="n">Card</span><span class="p">(</span>
            <span class="nl">margin:</span> <span class="kd">const</span> <span class="n">EdgeInsets</span><span class="o">.</span><span class="na">symmetric</span><span class="p">(</span><span class="nl">horizontal:</span> <span class="mi">12</span><span class="p">,</span> <span class="nl">vertical:</span> <span class="mi">4</span><span class="p">),</span>
            <span class="nl">child:</span> <span class="n">ListTile</span><span class="p">(</span>
              <span class="nl">leading:</span> <span class="n">CircleAvatar</span><span class="p">(</span>
                <span class="nl">backgroundColor:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">indigo</span><span class="o">.</span><span class="na">shade100</span><span class="p">,</span>
                <span class="nl">child:</span> <span class="n">Text</span><span class="p">(</span>
                  <span class="n">contact</span><span class="p">[</span><span class="s">'name'</span><span class="p">]</span><span class="o">!</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span>
                  <span class="nl">style:</span> <span class="kd">const</span> <span class="n">TextStyle</span><span class="p">(</span><span class="nl">fontWeight:</span> <span class="n">FontWeight</span><span class="o">.</span><span class="na">bold</span><span class="p">),</span>
                <span class="p">),</span>
              <span class="p">),</span>
              <span class="nl">title:</span> <span class="n">Text</span><span class="p">(</span><span class="n">contact</span><span class="p">[</span><span class="s">'name'</span><span class="p">]</span><span class="o">!</span><span class="p">),</span>
              <span class="nl">subtitle:</span> <span class="n">Text</span><span class="p">(</span><span class="n">contact</span><span class="p">[</span><span class="s">'role'</span><span class="p">]</span><span class="o">!</span><span class="p">),</span>
              <span class="nl">trailing:</span> <span class="kd">const</span> <span class="n">Icon</span><span class="p">(</span><span class="n">Icons</span><span class="o">.</span><span class="na">chevron_right</span><span class="p">),</span>
              <span class="nl">onTap:</span> <span class="p">()</span> <span class="p">{</span>
                <span class="n">ScaffoldMessenger</span><span class="o">.</span><span class="na">of</span><span class="p">(</span><span class="n">context</span><span class="p">)</span><span class="o">.</span><span class="na">showSnackBar</span><span class="p">(</span>
                  <span class="n">SnackBar</span><span class="p">(</span><span class="nl">content:</span> <span class="n">Text</span><span class="p">(</span><span class="s">'Klik: </span><span class="si">${contact['name']}</span><span class="s">'</span><span class="p">)),</span>
                <span class="p">);</span>
              <span class="p">},</span>
            <span class="p">),</span>
          <span class="p">);</span>
        <span class="p">},</span>
      <span class="p">),</span>
    <span class="p">);</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="kunci-listviewbuilder">Kunci ListView.builder</h3>

<table>
  <thead>
    <tr>
      <th>Parameter</th>
      <th>Wajib?</th>
      <th>Fungsi</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">itemCount</code></td>
      <td>Ya (untuk fix-length)</td>
      <td>Jumlah total item</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">itemBuilder</code></td>
      <td>Ya</td>
      <td>Callback yang dipanggil per item, nerima <code class="language-plaintext highlighter-rouge">(context, index)</code></td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">itemExtent</code></td>
      <td>Tidak</td>
      <td>Kalau semua item tingginya sama, set ini biar makin kencang</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">scrollDirection</code></td>
      <td>Tidak</td>
      <td>Default <code class="language-plaintext highlighter-rouge">Axis.vertical</code>, bisa juga <code class="language-plaintext highlighter-rouge">Axis.horizontal</code></td>
    </tr>
  </tbody>
</table>

<blockquote>
  <p><strong>Pro tip:</strong> Kalau tinggi semua item seragam (misal 80px), tambahin <code class="language-plaintext highlighter-rouge">itemExtent: 80</code>. Flutter gak perlu ngukur tinggi satu per satu → scrolling lebih mulus.</p>
</blockquote>

<hr />

<h2 id="3-listviewseparated--tambahin-pembatas">3. ListView.separated — Tambahin Pembatas</h2>

<p>Mau ada divider di antara item? <code class="language-plaintext highlighter-rouge">ListView.separated</code> jawabannya:</p>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">ListView</span><span class="o">.</span><span class="na">separated</span><span class="p">(</span>
  <span class="nl">itemCount:</span> <span class="n">_contacts</span><span class="o">.</span><span class="na">length</span><span class="p">,</span>
  <span class="nl">separatorBuilder:</span> <span class="p">(</span><span class="n">context</span><span class="p">,</span> <span class="n">index</span><span class="p">)</span> <span class="o">=</span><span class="p">&gt;</span> <span class="kd">const</span> <span class="n">Divider</span><span class="p">(</span>
    <span class="nl">height:</span> <span class="mi">1</span><span class="p">,</span>
    <span class="nl">indent:</span> <span class="mi">72</span><span class="p">,</span>  <span class="c1">// biar rata dengan text, bukan avatar</span>
  <span class="p">),</span>
  <span class="nl">itemBuilder:</span> <span class="p">(</span><span class="n">context</span><span class="p">,</span> <span class="n">index</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">final</span> <span class="n">contact</span> <span class="o">=</span> <span class="n">_contacts</span><span class="p">[</span><span class="n">index</span><span class="p">];</span>
    <span class="k">return</span> <span class="n">ListTile</span><span class="p">(</span>
      <span class="nl">leading:</span> <span class="n">CircleAvatar</span><span class="p">(</span>
        <span class="nl">backgroundColor:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">indigo</span><span class="o">.</span><span class="na">shade100</span><span class="p">,</span>
        <span class="nl">child:</span> <span class="n">Text</span><span class="p">(</span><span class="n">contact</span><span class="p">[</span><span class="s">'name'</span><span class="p">]</span><span class="o">!</span><span class="p">[</span><span class="mi">0</span><span class="p">]),</span>
      <span class="p">),</span>
      <span class="nl">title:</span> <span class="n">Text</span><span class="p">(</span><span class="n">contact</span><span class="p">[</span><span class="s">'name'</span><span class="p">]</span><span class="o">!</span><span class="p">),</span>
      <span class="nl">subtitle:</span> <span class="n">Text</span><span class="p">(</span><span class="n">contact</span><span class="p">[</span><span class="s">'role'</span><span class="p">]</span><span class="o">!</span><span class="p">),</span>
    <span class="p">);</span>
  <span class="p">},</span>
<span class="p">)</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">separatorBuilder</code> cuma dipanggil di <strong>antara</strong> item — bukan di awal atau akhir. Simpel tapi powerful.</p>

<hr />

<h2 id="4-gridview--saat-konten-perlu-tampil-kotak-kotak">4. GridView — Saat Konten Perlu Tampil “Kotak-Kotak”</h2>

<p>Kalau kontenmu lebih cocok tampil dalam grid (galeri foto, katalog produk, menu), pakai <code class="language-plaintext highlighter-rouge">GridView</code>. Mirip <code class="language-plaintext highlighter-rouge">ListView</code>, ada beberapa varian:</p>

<h3 id="gridviewcount--grid-dengan-jumlah-kolom-fix">GridView.count — Grid dengan Jumlah Kolom Fix</h3>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="s">'package:flutter/material.dart'</span><span class="o">;</span>

<span class="kt">void</span> <span class="nf">main</span><span class="p">()</span> <span class="o">=</span><span class="p">&gt;</span> <span class="n">runApp</span><span class="p">(</span><span class="kd">const</span> <span class="n">MaterialApp</span><span class="p">(</span><span class="nl">home:</span> <span class="n">GridBasicScreen</span><span class="p">()));</span>

<span class="kd">class</span> <span class="nc">GridBasicScreen</span> <span class="kd">extends</span> <span class="n">StatelessWidget</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="n">GridBasicScreen</span><span class="p">({</span><span class="k">super</span><span class="o">.</span><span class="na">key</span><span class="p">});</span>

  <span class="kd">final</span> <span class="kt">List</span><span class="p">&lt;</span><span class="kt">Map</span><span class="p">&lt;</span><span class="kt">String</span><span class="p">,</span> <span class="kd">dynamic</span><span class="p">&gt;&gt;</span> <span class="n">_products</span> <span class="o">=</span> <span class="kd">const</span> <span class="p">[</span>
    <span class="p">{</span><span class="s">'icon'</span><span class="o">:</span> <span class="n">Icons</span><span class="o">.</span><span class="na">phone_android</span><span class="p">,</span> <span class="s">'name'</span><span class="o">:</span> <span class="s">'Smartphone'</span><span class="p">,</span> <span class="s">'color'</span><span class="o">:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">blue</span><span class="p">},</span>
    <span class="p">{</span><span class="s">'icon'</span><span class="o">:</span> <span class="n">Icons</span><span class="o">.</span><span class="na">laptop</span><span class="p">,</span> <span class="s">'name'</span><span class="o">:</span> <span class="s">'Laptop'</span><span class="p">,</span> <span class="s">'color'</span><span class="o">:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">teal</span><span class="p">},</span>
    <span class="p">{</span><span class="s">'icon'</span><span class="o">:</span> <span class="n">Icons</span><span class="o">.</span><span class="na">watch</span><span class="p">,</span> <span class="s">'name'</span><span class="o">:</span> <span class="s">'Smartwatch'</span><span class="p">,</span> <span class="s">'color'</span><span class="o">:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">orange</span><span class="p">},</span>
    <span class="p">{</span><span class="s">'icon'</span><span class="o">:</span> <span class="n">Icons</span><span class="o">.</span><span class="na">headphones</span><span class="p">,</span> <span class="s">'name'</span><span class="o">:</span> <span class="s">'Headset'</span><span class="p">,</span> <span class="s">'color'</span><span class="o">:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">purple</span><span class="p">},</span>
    <span class="p">{</span><span class="s">'icon'</span><span class="o">:</span> <span class="n">Icons</span><span class="o">.</span><span class="na">camera_alt</span><span class="p">,</span> <span class="s">'name'</span><span class="o">:</span> <span class="s">'Kamera'</span><span class="p">,</span> <span class="s">'color'</span><span class="o">:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">red</span><span class="p">},</span>
    <span class="p">{</span><span class="s">'icon'</span><span class="o">:</span> <span class="n">Icons</span><span class="o">.</span><span class="na">tablet</span><span class="p">,</span> <span class="s">'name'</span><span class="o">:</span> <span class="s">'Tablet'</span><span class="p">,</span> <span class="s">'color'</span><span class="o">:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">green</span><span class="p">},</span>
    <span class="p">{</span><span class="s">'icon'</span><span class="o">:</span> <span class="n">Icons</span><span class="o">.</span><span class="na">speaker</span><span class="p">,</span> <span class="s">'name'</span><span class="o">:</span> <span class="s">'Speaker'</span><span class="p">,</span> <span class="s">'color'</span><span class="o">:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">brown</span><span class="p">},</span>
    <span class="p">{</span><span class="s">'icon'</span><span class="o">:</span> <span class="n">Icons</span><span class="o">.</span><span class="na">keyboard</span><span class="p">,</span> <span class="s">'name'</span><span class="o">:</span> <span class="s">'Keyboard'</span><span class="p">,</span> <span class="s">'color'</span><span class="o">:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">indigo</span><span class="p">},</span>
    <span class="p">{</span><span class="s">'icon'</span><span class="o">:</span> <span class="n">Icons</span><span class="o">.</span><span class="na">mouse</span><span class="p">,</span> <span class="s">'name'</span><span class="o">:</span> <span class="s">'Mouse'</span><span class="p">,</span> <span class="s">'color'</span><span class="o">:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">pink</span><span class="p">},</span>
    <span class="p">{</span><span class="s">'icon'</span><span class="o">:</span> <span class="n">Icons</span><span class="o">.</span><span class="na">monitor</span><span class="p">,</span> <span class="s">'name'</span><span class="o">:</span> <span class="s">'Monitor'</span><span class="p">,</span> <span class="s">'color'</span><span class="o">:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">cyan</span><span class="p">},</span>
  <span class="p">];</span>

  <span class="nd">@override</span>
  <span class="n">Widget</span> <span class="n">build</span><span class="p">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span> <span class="n">Scaffold</span><span class="p">(</span>
      <span class="nl">appBar:</span> <span class="n">AppBar</span><span class="p">(</span>
        <span class="nl">title:</span> <span class="kd">const</span> <span class="n">Text</span><span class="p">(</span><span class="s">'GridView Katalog Produk'</span><span class="p">),</span>
        <span class="nl">centerTitle:</span> <span class="kc">true</span><span class="p">,</span>
      <span class="p">),</span>
      <span class="nl">body:</span> <span class="n">GridView</span><span class="o">.</span><span class="na">count</span><span class="p">(</span>
        <span class="nl">crossAxisCount:</span> <span class="mi">2</span><span class="p">,</span>           <span class="c1">// 2 kolom</span>
        <span class="nl">crossAxisSpacing:</span> <span class="mi">12</span><span class="p">,</span>        <span class="c1">// jarak horizontal antar item</span>
        <span class="nl">mainAxisSpacing:</span> <span class="mi">12</span><span class="p">,</span>         <span class="c1">// jarak vertikal antar item</span>
        <span class="nl">padding:</span> <span class="kd">const</span> <span class="n">EdgeInsets</span><span class="o">.</span><span class="na">all</span><span class="p">(</span><span class="mi">16</span><span class="p">),</span>
        <span class="nl">children:</span> <span class="n">_products</span><span class="o">.</span><span class="na">map</span><span class="p">((</span><span class="n">product</span><span class="p">)</span> <span class="p">{</span>
          <span class="k">return</span> <span class="n">Card</span><span class="p">(</span>
            <span class="nl">elevation:</span> <span class="mi">2</span><span class="p">,</span>
            <span class="nl">shape:</span> <span class="n">RoundedRectangleBorder</span><span class="p">(</span>
              <span class="nl">borderRadius:</span> <span class="n">BorderRadius</span><span class="o">.</span><span class="na">circular</span><span class="p">(</span><span class="mi">16</span><span class="p">),</span>
            <span class="p">),</span>
            <span class="nl">child:</span> <span class="n">InkWell</span><span class="p">(</span>
              <span class="nl">borderRadius:</span> <span class="n">BorderRadius</span><span class="o">.</span><span class="na">circular</span><span class="p">(</span><span class="mi">16</span><span class="p">),</span>
              <span class="nl">onTap:</span> <span class="p">()</span> <span class="p">{</span>
                <span class="n">ScaffoldMessenger</span><span class="o">.</span><span class="na">of</span><span class="p">(</span><span class="n">context</span><span class="p">)</span><span class="o">.</span><span class="na">showSnackBar</span><span class="p">(</span>
                  <span class="n">SnackBar</span><span class="p">(</span><span class="nl">content:</span> <span class="n">Text</span><span class="p">(</span><span class="s">'Kamu pilih: </span><span class="si">${product['name']}</span><span class="s">'</span><span class="p">)),</span>
                <span class="p">);</span>
              <span class="p">},</span>
              <span class="nl">child:</span> <span class="n">Column</span><span class="p">(</span>
                <span class="nl">mainAxisAlignment:</span> <span class="n">MainAxisAlignment</span><span class="o">.</span><span class="na">center</span><span class="p">,</span>
                <span class="nl">children:</span> <span class="p">[</span>
                  <span class="n">Icon</span><span class="p">(</span>
                    <span class="n">product</span><span class="p">[</span><span class="s">'icon'</span><span class="p">]</span> <span class="k">as</span> <span class="n">IconData</span><span class="p">,</span>
                    <span class="nl">size:</span> <span class="mi">48</span><span class="p">,</span>
                    <span class="nl">color:</span> <span class="n">product</span><span class="p">[</span><span class="s">'color'</span><span class="p">]</span> <span class="k">as</span> <span class="n">Color</span><span class="p">,</span>
                  <span class="p">),</span>
                  <span class="kd">const</span> <span class="n">SizedBox</span><span class="p">(</span><span class="nl">height:</span> <span class="mi">12</span><span class="p">),</span>
                  <span class="n">Text</span><span class="p">(</span>
                    <span class="n">product</span><span class="p">[</span><span class="s">'name'</span><span class="p">]</span> <span class="k">as</span> <span class="kt">String</span><span class="p">,</span>
                    <span class="nl">style:</span> <span class="kd">const</span> <span class="n">TextStyle</span><span class="p">(</span>
                      <span class="nl">fontWeight:</span> <span class="n">FontWeight</span><span class="o">.</span><span class="na">w600</span><span class="p">,</span>
                      <span class="nl">fontSize:</span> <span class="mi">16</span><span class="p">,</span>
                    <span class="p">),</span>
                  <span class="p">),</span>
                <span class="p">],</span>
              <span class="p">),</span>
            <span class="p">),</span>
          <span class="p">);</span>
        <span class="p">})</span><span class="o">.</span><span class="na">toList</span><span class="p">(),</span>
      <span class="p">),</span>
    <span class="p">);</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="gridviewbuilder--buat-data-banyak">GridView.builder — Buat Data Banyak</h3>

<p>Sama seperti <code class="language-plaintext highlighter-rouge">ListView.builder</code>, ada <code class="language-plaintext highlighter-rouge">GridView.builder</code> untuk data yang banyak:</p>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">GridView</span><span class="o">.</span><span class="na">builder</span><span class="p">(</span>
  <span class="nl">gridDelegate:</span> <span class="kd">const</span> <span class="n">SliverGridDelegateWithFixedCrossAxisCount</span><span class="p">(</span>
    <span class="nl">crossAxisCount:</span> <span class="mi">3</span><span class="p">,</span>       <span class="c1">// 3 kolom</span>
    <span class="nl">crossAxisSpacing:</span> <span class="mi">8</span><span class="p">,</span>
    <span class="nl">mainAxisSpacing:</span> <span class="mi">8</span><span class="p">,</span>
    <span class="nl">childAspectRatio:</span> <span class="mf">0.85</span><span class="p">,</span>  <span class="c1">// ratio width:height (0.85 = lebih tinggi dari lebar)</span>
  <span class="p">),</span>
  <span class="nl">itemCount:</span> <span class="n">_products</span><span class="o">.</span><span class="na">length</span><span class="p">,</span>
  <span class="nl">itemBuilder:</span> <span class="p">(</span><span class="n">context</span><span class="p">,</span> <span class="n">index</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">final</span> <span class="n">product</span> <span class="o">=</span> <span class="n">_products</span><span class="p">[</span><span class="n">index</span><span class="p">];</span>
    <span class="k">return</span> <span class="n">Card</span><span class="p">(</span>
      <span class="nl">child:</span> <span class="n">Column</span><span class="p">(</span>
        <span class="nl">mainAxisAlignment:</span> <span class="n">MainAxisAlignment</span><span class="o">.</span><span class="na">center</span><span class="p">,</span>
        <span class="nl">children:</span> <span class="p">[</span>
          <span class="n">Icon</span><span class="p">(</span><span class="n">product</span><span class="p">[</span><span class="s">'icon'</span><span class="p">]</span> <span class="k">as</span> <span class="n">IconData</span><span class="p">,</span> <span class="nl">size:</span> <span class="mi">36</span><span class="p">,</span> <span class="nl">color:</span> <span class="n">product</span><span class="p">[</span><span class="s">'color'</span><span class="p">]</span> <span class="k">as</span> <span class="n">Color</span><span class="p">),</span>
          <span class="kd">const</span> <span class="n">SizedBox</span><span class="p">(</span><span class="nl">height:</span> <span class="mi">8</span><span class="p">),</span>
          <span class="n">Text</span><span class="p">(</span><span class="n">product</span><span class="p">[</span><span class="s">'name'</span><span class="p">]</span> <span class="k">as</span> <span class="kt">String</span><span class="p">,</span> <span class="nl">textAlign:</span> <span class="n">TextAlign</span><span class="o">.</span><span class="na">center</span><span class="p">),</span>
        <span class="p">],</span>
      <span class="p">),</span>
    <span class="p">);</span>
  <span class="p">},</span>
<span class="p">)</span>
</code></pre></div></div>

<h3 id="griddelegate--kunci-flexibilitas-grid">GridDelegate — Kunci Flexibilitas Grid</h3>

<p>Ada dua delegate penting:</p>

<table>
  <thead>
    <tr>
      <th>Delegate</th>
      <th>Fungsi</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">SliverGridDelegateWithFixedCrossAxisCount</code></td>
      <td>Jumlah kolom fix (2, 3, 4 kolom)</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">SliverGridDelegateWithMaxCrossAxisExtent</code></td>
      <td>Lebar maksimal per item fix, jumlah kolom menyesuaikan</td>
    </tr>
  </tbody>
</table>

<p>Contoh <code class="language-plaintext highlighter-rouge">MaxCrossAxisExtent</code> (responsif!):</p>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">SliverGridDelegateWithMaxCrossAxisExtent</span><span class="p">(</span>
  <span class="nl">maxCrossAxisExtent:</span> <span class="mi">180</span><span class="p">,</span>   <span class="c1">// maks 180 pixel per item → di layar lebar bisa 4 kolom</span>
  <span class="nl">crossAxisSpacing:</span> <span class="mi">10</span><span class="p">,</span>
  <span class="nl">mainAxisSpacing:</span> <span class="mi">10</span><span class="p">,</span>
  <span class="nl">childAspectRatio:</span> <span class="mf">1.0</span><span class="p">,</span>     <span class="c1">// kotak sempurna</span>
<span class="p">)</span>
</code></pre></div></div>

<p>Ini berguna banget buat layout responsif: di HP muncul 2 kolom, di tablet 4 kolom — otomatis tanpa media query.</p>

<hr />

<h2 id="5-horizontal-scroll--bikin-carousel-ala-netflix">5. Horizontal Scroll — Bikin Carousel ala Netflix</h2>

<p>ListView dan GridView juga bisa horizontal! Tinggal tambah <code class="language-plaintext highlighter-rouge">scrollDirection: Axis.horizontal</code>:</p>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">SizedBox</span><span class="p">(</span>
  <span class="nl">height:</span> <span class="mi">180</span><span class="p">,</span>
  <span class="nl">child:</span> <span class="n">ListView</span><span class="o">.</span><span class="na">builder</span><span class="p">(</span>
    <span class="nl">scrollDirection:</span> <span class="n">Axis</span><span class="o">.</span><span class="na">horizontal</span><span class="p">,</span>
    <span class="nl">itemCount:</span> <span class="n">_products</span><span class="o">.</span><span class="na">length</span><span class="p">,</span>
    <span class="nl">padding:</span> <span class="kd">const</span> <span class="n">EdgeInsets</span><span class="o">.</span><span class="na">symmetric</span><span class="p">(</span><span class="nl">horizontal:</span> <span class="mi">12</span><span class="p">),</span>
    <span class="nl">itemBuilder:</span> <span class="p">(</span><span class="n">context</span><span class="p">,</span> <span class="n">index</span><span class="p">)</span> <span class="p">{</span>
      <span class="kd">final</span> <span class="n">product</span> <span class="o">=</span> <span class="n">_products</span><span class="p">[</span><span class="n">index</span><span class="p">];</span>
      <span class="k">return</span> <span class="n">Container</span><span class="p">(</span>
        <span class="nl">width:</span> <span class="mi">140</span><span class="p">,</span>
        <span class="nl">margin:</span> <span class="kd">const</span> <span class="n">EdgeInsets</span><span class="o">.</span><span class="na">only</span><span class="p">(</span><span class="nl">right:</span> <span class="mi">12</span><span class="p">),</span>
        <span class="nl">decoration:</span> <span class="n">BoxDecoration</span><span class="p">(</span>
          <span class="nl">color:</span> <span class="p">(</span><span class="n">product</span><span class="p">[</span><span class="s">'color'</span><span class="p">]</span> <span class="k">as</span> <span class="n">Color</span><span class="p">)</span><span class="o">.</span><span class="na">withOpacity</span><span class="p">(</span><span class="mf">0.1</span><span class="p">),</span>
          <span class="nl">borderRadius:</span> <span class="n">BorderRadius</span><span class="o">.</span><span class="na">circular</span><span class="p">(</span><span class="mi">16</span><span class="p">),</span>
          <span class="nl">border:</span> <span class="n">Border</span><span class="o">.</span><span class="na">all</span><span class="p">(</span><span class="nl">color:</span> <span class="n">product</span><span class="p">[</span><span class="s">'color'</span><span class="p">]</span> <span class="k">as</span> <span class="n">Color</span><span class="p">,</span> <span class="nl">width:</span> <span class="mi">2</span><span class="p">),</span>
        <span class="p">),</span>
        <span class="nl">child:</span> <span class="n">Column</span><span class="p">(</span>
          <span class="nl">mainAxisAlignment:</span> <span class="n">MainAxisAlignment</span><span class="o">.</span><span class="na">center</span><span class="p">,</span>
          <span class="nl">children:</span> <span class="p">[</span>
            <span class="n">Icon</span><span class="p">(</span><span class="n">product</span><span class="p">[</span><span class="s">'icon'</span><span class="p">]</span> <span class="k">as</span> <span class="n">IconData</span><span class="p">,</span> <span class="nl">size:</span> <span class="mi">48</span><span class="p">,</span> <span class="nl">color:</span> <span class="n">product</span><span class="p">[</span><span class="s">'color'</span><span class="p">]</span> <span class="k">as</span> <span class="n">Color</span><span class="p">),</span>
            <span class="kd">const</span> <span class="n">SizedBox</span><span class="p">(</span><span class="nl">height:</span> <span class="mi">8</span><span class="p">),</span>
            <span class="n">Text</span><span class="p">(</span><span class="n">product</span><span class="p">[</span><span class="s">'name'</span><span class="p">]</span> <span class="k">as</span> <span class="kt">String</span><span class="p">,</span> <span class="nl">textAlign:</span> <span class="n">TextAlign</span><span class="o">.</span><span class="na">center</span><span class="p">),</span>
          <span class="p">],</span>
        <span class="p">),</span>
      <span class="p">);</span>
    <span class="p">},</span>
  <span class="p">),</span>
<span class="p">)</span>
</code></pre></div></div>

<p>Bungkus dengan <code class="language-plaintext highlighter-rouge">SizedBox(height: ...)</code> karena horizontal ListView butuh constraint tinggi dari parent.</p>

<hr />

<h2 id="6-pull-to-refresh--sentuhan-akhir">6. Pull-to-Refresh — Sentuhan Akhir</h2>

<p>Tambahkan <code class="language-plaintext highlighter-rouge">RefreshIndicator</code> supaya user bisa swipe-down untuk refresh:</p>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">RefreshIndicator</span><span class="p">(</span>
  <span class="nl">onRefresh:</span> <span class="p">()</span> <span class="kd">async</span> <span class="p">{</span>
    <span class="c1">// panggil API / reload data</span>
    <span class="k">await</span> <span class="n">Future</span><span class="o">.</span><span class="na">delayed</span><span class="p">(</span><span class="kd">const</span> <span class="n">Duration</span><span class="p">(</span><span class="nl">seconds:</span> <span class="mi">1</span><span class="p">));</span>
  <span class="p">},</span>
  <span class="nl">child:</span> <span class="n">ListView</span><span class="o">.</span><span class="na">builder</span><span class="p">(</span>
    <span class="nl">itemCount:</span> <span class="n">_contacts</span><span class="o">.</span><span class="na">length</span><span class="p">,</span>
    <span class="nl">itemBuilder:</span> <span class="p">(</span><span class="n">context</span><span class="p">,</span> <span class="n">index</span><span class="p">)</span> <span class="p">{</span> <span class="p">...</span> <span class="p">},</span>
  <span class="p">),</span>
<span class="p">)</span>
</code></pre></div></div>

<hr />

<h2 id="7-perbandingan-lengkap-kapan-pakai-yang-mana">7. Perbandingan Lengkap: Kapan Pakai yang Mana?</h2>

<table>
  <thead>
    <tr>
      <th>Kasus</th>
      <th>Widget</th>
      <th>Alasan</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>List pendek, fix (&lt; 20 item)</td>
      <td><code class="language-plaintext highlighter-rouge">ListView(children: [...])</code></td>
      <td>Simpel, gak perlu builder</td>
    </tr>
    <tr>
      <td>List panjang, banyak data</td>
      <td><code class="language-plaintext highlighter-rouge">ListView.builder</code></td>
      <td>Lazy loading, performa optimal</td>
    </tr>
    <tr>
      <td>List dengan divider</td>
      <td><code class="language-plaintext highlighter-rouge">ListView.separated</code></td>
      <td><code class="language-plaintext highlighter-rouge">separatorBuilder</code> built-in</td>
    </tr>
    <tr>
      <td>Grid jumlah kolom fix</td>
      <td><code class="language-plaintext highlighter-rouge">GridView.count</code> / <code class="language-plaintext highlighter-rouge">.builder</code> + <code class="language-plaintext highlighter-rouge">FixedCrossAxisCount</code></td>
      <td>Kolom statis</td>
    </tr>
    <tr>
      <td>Grid responsif</td>
      <td><code class="language-plaintext highlighter-rouge">.builder</code> + <code class="language-plaintext highlighter-rouge">MaxCrossAxisExtent</code></td>
      <td>Kolom menyesuaikan lebar layar</td>
    </tr>
    <tr>
      <td>Carousel / horizontal scroll</td>
      <td><code class="language-plaintext highlighter-rouge">ListView(horizontal)</code> / <code class="language-plaintext highlighter-rouge">.builder</code></td>
      <td>Bungkus dengan <code class="language-plaintext highlighter-rouge">SizedBox(height)</code></td>
    </tr>
    <tr>
      <td>Galeri foto / grid padat</td>
      <td><code class="language-plaintext highlighter-rouge">GridView.builder</code></td>
      <td>Hanya render yang terlihat</td>
    </tr>
  </tbody>
</table>

<hr />

<h2 id="8-common-mistakes-jangan-diulang">8. Common Mistakes (Jangan Diulang!)</h2>

<h3 id="-listview-di-dalam-column-tanpa-expanded">❌ ListView di dalam Column tanpa Expanded</h3>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// ERROR: ListView gak tahu tinggi maksimalnya</span>
<span class="n">Column</span><span class="p">(</span>
  <span class="nl">children:</span> <span class="p">[</span>
    <span class="n">Text</span><span class="p">(</span><span class="s">'Judul'</span><span class="p">),</span>
    <span class="n">ListView</span><span class="o">.</span><span class="na">builder</span><span class="p">(...),</span>  <span class="c1">// ❌ unbounded height error!</span>
  <span class="p">],</span>
<span class="p">)</span>

<span class="c1">// ✅ BENER: bungkus dengan Expanded</span>
<span class="n">Column</span><span class="p">(</span>
  <span class="nl">children:</span> <span class="p">[</span>
    <span class="n">Text</span><span class="p">(</span><span class="s">'Judul'</span><span class="p">),</span>
    <span class="n">Expanded</span><span class="p">(</span>
      <span class="nl">child:</span> <span class="n">ListView</span><span class="o">.</span><span class="na">builder</span><span class="p">(...),</span>  <span class="c1">// ✅ dapat constraint tinggi</span>
    <span class="p">),</span>
  <span class="p">],</span>
<span class="p">)</span>
</code></pre></div></div>

<h3 id="-pakai-listviewchildren--untuk-ratusan-item">❌ Pakai <code class="language-plaintext highlighter-rouge">ListView(children: [])</code> untuk ratusan item</h3>

<p>Kalau datanya dari API, <strong>selalu</strong> pakai <code class="language-plaintext highlighter-rouge">.builder</code>. Memory dan performa kamu akan berterima kasih.</p>

<h3 id="-lupa-itemcount-di-listviewbuilder">❌ Lupa <code class="language-plaintext highlighter-rouge">itemCount</code> di <code class="language-plaintext highlighter-rouge">ListView.builder</code></h3>

<p>Tanpa <code class="language-plaintext highlighter-rouge">itemCount</code>, Flutter anggap list-nya infinite. Scroll bar bisa aneh, dan kalau index out of bounds → crash.</p>

<hr />

<h2 id="9-latihan-mandiri">9. Latihan Mandiri</h2>

<p>Coba implementasikan tiga latihan ini sendiri:</p>

<ol>
  <li><strong>Daftar kontak dari JSON</strong>: Bikin model class <code class="language-plaintext highlighter-rouge">Contact</code>, parse JSON lokal, tampilkan di <code class="language-plaintext highlighter-rouge">ListView.separated</code> dengan avatar, nama, dan nomor telepon.</li>
  <li><strong>Katalog produk e-commerce</strong>: <code class="language-plaintext highlighter-rouge">GridView.builder</code> 2 kolom dengan gambar, nama produk, harga, dan rating bintang.</li>
  <li><strong>Horizontal story carousel</strong>: <code class="language-plaintext highlighter-rouge">ListView</code> horizontal menampilkan circle avatar dengan nama di bawahnya, seperti Instagram Stories.</li>
</ol>

<hr />

<h2 id="penutup">Penutup</h2>

<p>Hari ini kita udah menguasai dua widget paling fundamental untuk konten scrollable: <strong>ListView</strong> dan <strong>GridView</strong>. Dari yang simpel (<code class="language-plaintext highlighter-rouge">children: []</code>) sampai yang performa juara (<code class="language-plaintext highlighter-rouge">.builder</code>), dari scroll vertikal ke horizontal, dari list ke grid responsif — semuanya udah di tangan kamu.</p>

<p>Materi ini bakal terus kepakai di sepanjang perjalanan Flutter kamu. Feed berita? Pakai ListView. Katalog produk? Pakai GridView. Stories? Horizontal ListView. Inbox chat? ListView lagi.</p>

<p>Di artikel berikutnya kita akan bahas <strong>Navigasi</strong>: pindah-pindah halaman dengan <code class="language-plaintext highlighter-rouge">Navigator.push</code>, named routes, dan passing data antar screen. Stay tuned!</p>

<p>Coba sendiri! Implementasikan tiga latihan di atas, share hasilnya ke sosial media, dan tag <strong>@ahsai001</strong> 🚀</p>]]></content><author><name></name></author><category term="flutter" /><category term="dart" /><category term="listview" /><category term="gridview" /><category term="scroll" /><category term="tutorial" /><category term="indonesia" /><category term="pemrograman" /><summary type="html"><![CDATA[Aplikasi yang cuma satu layar statis itu ibarat warung kosong — sepi, gak ada yang bisa di-scroll. Nah, dua widget paling penting untuk bikin konten yang bisa digulir adalah ListView dan GridView. Mau bikin feed berita, daftar kontak, galeri foto, sampai katalog produk? Dua inilah senjata utamamu.]]></summary></entry><entry><title type="html">Flutter #6: Layouting — Padding, Margin, SizedBox, Expanded &amp;amp; Flexible</title><link href="https://ahsai.my.id/blog/2026/08/08/flutter-6-layouting-padding-margin-sizedbox-expanded-flexible/" rel="alternate" type="text/html" title="Flutter #6: Layouting — Padding, Margin, SizedBox, Expanded &amp;amp; Flexible" /><published>2026-08-08T00:00:00+00:00</published><updated>2026-08-08T00:00:00+00:00</updated><id>https://ahsai.my.id/blog/2026/08/08/flutter-6-layouting-padding-margin-sizedbox-expanded-flexible</id><content type="html" xml:base="https://ahsai.my.id/blog/2026/08/08/flutter-6-layouting-padding-margin-sizedbox-expanded-flexible/"><![CDATA[<p>Kalau widget adalah bata, layouting adalah cara kita menyusun bata itu jadi rumah yang rapi. Di Flutter, urusan tata letak itu hampir semuanya pakai widget juga — dan justru di situlah kekuatannya. Nggak ada XML layout terpisah, nggak ada constraint-based layout yang bikin pusing. Semua adalah widget, semua composable.</p>

<p>Hari ini kita bahas lima “alat” layouting paling fundamental: <strong>Padding &amp; Margin</strong>, <strong>SizedBox</strong>, <strong>Expanded</strong>, dan <strong>Flexible</strong>. Kuasai kelimanya, dan kamu bisa bikin layout apapun.</p>

<hr />

<h2 id="1-padding--margin--bedanya-apa">1. Padding &amp; Margin — Bedanya Apa?</h2>

<p>Di Flutter, konsep “margin” dan “padding” disatukan dalam satu widget: <strong><code class="language-plaintext highlighter-rouge">Padding</code></strong> dan properti <strong><code class="language-plaintext highlighter-rouge">margin</code></strong> pada <code class="language-plaintext highlighter-rouge">Container</code>. Bedanya sederhana:</p>

<ul>
  <li><strong>Padding</strong> = ruang <em>di dalam</em> widget, antara border dan child</li>
  <li><strong>Margin</strong> = ruang <em>di luar</em> widget, antara widget dan parent/saudaranya</li>
</ul>

<h3 id="padding-dengan-widget-padding">Padding dengan Widget <code class="language-plaintext highlighter-rouge">Padding</code></h3>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Padding</span><span class="p">(</span>
  <span class="nl">padding:</span> <span class="kd">const</span> <span class="n">EdgeInsets</span><span class="o">.</span><span class="na">all</span><span class="p">(</span><span class="mf">16.0</span><span class="p">),</span>
  <span class="nl">child:</span> <span class="n">Container</span><span class="p">(</span>
    <span class="nl">color:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">blue</span><span class="p">,</span>
    <span class="nl">child:</span> <span class="kd">const</span> <span class="n">Text</span><span class="p">(</span><span class="s">'Ada jarak 16 di semua sisi'</span><span class="p">),</span>
  <span class="p">),</span>
<span class="p">)</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">EdgeInsets</code> punya beberapa constructor yang berguna:</p>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">EdgeInsets</span><span class="o">.</span><span class="na">all</span><span class="p">(</span><span class="mi">16</span><span class="p">)</span>           <span class="c1">// semua sisi 16</span>
<span class="n">EdgeInsets</span><span class="o">.</span><span class="na">symmetric</span><span class="p">(</span>        <span class="c1">// horizontal 24, vertical 12</span>
  <span class="nl">horizontal:</span> <span class="mi">24</span><span class="p">,</span>
  <span class="nl">vertical:</span> <span class="mi">12</span><span class="p">,</span>
<span class="p">)</span>
<span class="n">EdgeInsets</span><span class="o">.</span><span class="na">only</span><span class="p">(</span>             <span class="c1">// hanya kiri &amp; atas</span>
  <span class="nl">left:</span> <span class="mi">32</span><span class="p">,</span>
  <span class="nl">top:</span> <span class="mi">8</span><span class="p">,</span>
<span class="p">)</span>
<span class="n">EdgeInsets</span><span class="o">.</span><span class="na">fromLTRB</span><span class="p">(</span><span class="mi">8</span><span class="p">,</span> <span class="mi">16</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="mi">16</span><span class="p">)</span> <span class="c1">// left, top, right, bottom</span>
</code></pre></div></div>

<h3 id="margin-dengan-container">Margin dengan <code class="language-plaintext highlighter-rouge">Container</code></h3>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Container</span><span class="p">(</span>
  <span class="nl">margin:</span> <span class="kd">const</span> <span class="n">EdgeInsets</span><span class="o">.</span><span class="na">symmetric</span><span class="p">(</span><span class="nl">horizontal:</span> <span class="mi">20</span><span class="p">,</span> <span class="nl">vertical:</span> <span class="mi">10</span><span class="p">),</span>
  <span class="nl">padding:</span> <span class="kd">const</span> <span class="n">EdgeInsets</span><span class="o">.</span><span class="na">all</span><span class="p">(</span><span class="mi">12</span><span class="p">),</span>
  <span class="nl">color:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">grey</span><span class="p">[</span><span class="mi">200</span><span class="p">],</span>
  <span class="nl">child:</span> <span class="kd">const</span> <span class="n">Text</span><span class="p">(</span><span class="s">'Margin luar 20 horizontal, padding dalam 12'</span><span class="p">),</span>
<span class="p">)</span>
</code></pre></div></div>

<blockquote>
  <p><strong>Tips:</strong> Kalau cuma butuh padding tanpa dekorasi lain, pakai widget <code class="language-plaintext highlighter-rouge">Padding</code> langsung. <code class="language-plaintext highlighter-rouge">Container</code> lebih “berat” karena punya banyak properti. Untuk kasus simpel, <code class="language-plaintext highlighter-rouge">Padding</code> lebih ringan secara rendering.</p>
</blockquote>

<h3 id="visualisasi-box-model">Visualisasi Box Model</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>┌────────── MARGIN ──────────┐
│  ┌────── BORDER ──────┐   │
│  │  ┌── PADDING ──┐   │   │
│  │  │              │   │   │
│  │  │   CONTENT    │   │   │
│  │  │              │   │   │
│  │  └──────────────┘   │   │
│  └─────────────────────┘   │
└─────────────────────────────┘
</code></pre></div></div>

<hr />

<h2 id="2-sizedbox--bikin-jarak--ukuran-fix">2. SizedBox — Bikin Jarak &amp; Ukuran Fix</h2>

<p><code class="language-plaintext highlighter-rouge">SizedBox</code> adalah widget paling sederhana untuk membuat <em>ruang kosong</em> dengan ukuran tertentu. Dua kegunaan utama:</p>

<h3 id="sebagai-spacer-jarak-antar-widget">Sebagai Spacer (Jarak Antar Widget)</h3>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Column</span><span class="p">(</span>
  <span class="nl">children:</span> <span class="p">[</span>
    <span class="n">ElevatedButton</span><span class="p">(</span><span class="nl">onPressed:</span> <span class="p">()</span> <span class="p">{},</span> <span class="nl">child:</span> <span class="kd">const</span> <span class="n">Text</span><span class="p">(</span><span class="s">'Tombol 1'</span><span class="p">)),</span>
    <span class="kd">const</span> <span class="n">SizedBox</span><span class="p">(</span><span class="nl">height:</span> <span class="mi">16</span><span class="p">),</span>  <span class="c1">// jarak vertikal 16 pixel</span>
    <span class="n">ElevatedButton</span><span class="p">(</span><span class="nl">onPressed:</span> <span class="p">()</span> <span class="p">{},</span> <span class="nl">child:</span> <span class="kd">const</span> <span class="n">Text</span><span class="p">(</span><span class="s">'Tombol 2'</span><span class="p">)),</span>
  <span class="p">],</span>
<span class="p">)</span>
</code></pre></div></div>

<h3 id="sebagai-fixed-size-container">Sebagai Fixed-Size Container</h3>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">SizedBox</span><span class="p">(</span>
  <span class="nl">width:</span> <span class="mi">200</span><span class="p">,</span>
  <span class="nl">height:</span> <span class="mi">100</span><span class="p">,</span>
  <span class="nl">child:</span> <span class="n">ElevatedButton</span><span class="p">(</span>
    <span class="nl">onPressed:</span> <span class="p">()</span> <span class="p">{},</span>
    <span class="nl">child:</span> <span class="kd">const</span> <span class="n">Text</span><span class="p">(</span><span class="s">'Lebar fix 200, tinggi 100'</span><span class="p">),</span>
  <span class="p">),</span>
<span class="p">)</span>
</code></pre></div></div>

<h3 id="sizedbox-vs-container">SizedBox vs Container</h3>

<table>
  <thead>
    <tr>
      <th>Aspek</th>
      <th>SizedBox</th>
      <th>Container</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Fungsi utama</strong></td>
      <td>Ukuran fix atau spacer</td>
      <td>Multi-purpose box</td>
    </tr>
    <tr>
      <td><strong>Performance</strong></td>
      <td>Ringan</td>
      <td>Lebih berat</td>
    </tr>
    <tr>
      <td><strong>Dekorasi</strong></td>
      <td>❌ Tidak bisa</td>
      <td>✅ <code class="language-plaintext highlighter-rouge">decoration</code>, <code class="language-plaintext highlighter-rouge">borderRadius</code></td>
    </tr>
    <tr>
      <td><strong>Transform</strong></td>
      <td>❌ Tidak bisa</td>
      <td>✅ <code class="language-plaintext highlighter-rouge">transform</code></td>
    </tr>
    <tr>
      <td><strong>Gunakan saat</strong></td>
      <td>Cuma perlu ukuran</td>
      <td>Perlu styling (warna, border, shadow, dll)</td>
    </tr>
  </tbody>
</table>

<hr />

<h2 id="3-expanded--ambil-semua-sisa-ruang">3. Expanded — Ambil Semua Sisa Ruang</h2>

<p><code class="language-plaintext highlighter-rouge">Expanded</code> adalah widget yang <strong>mengambil semua sisa ruang</strong> dari parent <code class="language-plaintext highlighter-rouge">Row</code> atau <code class="language-plaintext highlighter-rouge">Column</code>. Ini adalah senjata utama untuk layout responsif.</p>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Row</span><span class="p">(</span>
  <span class="nl">children:</span> <span class="p">[</span>
    <span class="n">Container</span><span class="p">(</span><span class="nl">width:</span> <span class="mi">80</span><span class="p">,</span> <span class="nl">height:</span> <span class="mi">50</span><span class="p">,</span> <span class="nl">color:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">red</span><span class="p">),</span>
    <span class="kd">const</span> <span class="n">SizedBox</span><span class="p">(</span><span class="nl">width:</span> <span class="mi">8</span><span class="p">),</span>
    <span class="n">Expanded</span><span class="p">(</span>
      <span class="nl">child:</span> <span class="n">Container</span><span class="p">(</span><span class="nl">height:</span> <span class="mi">50</span><span class="p">,</span> <span class="nl">color:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">blue</span><span class="p">),</span>
    <span class="p">),</span>  <span class="c1">// ← mengambil sisa lebar setelah 80+8 pixel</span>
  <span class="p">],</span>
<span class="p">)</span>
</code></pre></div></div>

<h3 id="properti-flex--pembagian-proporsional">Properti <code class="language-plaintext highlighter-rouge">flex</code> — Pembagian Proporsional</h3>

<p>Kalau ada beberapa <code class="language-plaintext highlighter-rouge">Expanded</code>, properti <code class="language-plaintext highlighter-rouge">flex</code> menentukan rasio pembagiannya:</p>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Row</span><span class="p">(</span>
  <span class="nl">children:</span> <span class="p">[</span>
    <span class="n">Expanded</span><span class="p">(</span>
      <span class="nl">flex:</span> <span class="mi">2</span><span class="p">,</span>
      <span class="nl">child:</span> <span class="n">Container</span><span class="p">(</span><span class="nl">height:</span> <span class="mi">50</span><span class="p">,</span> <span class="nl">color:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">green</span><span class="p">),</span>
    <span class="p">),</span>  <span class="c1">// dapat 2/5 lebar</span>
    <span class="n">SizedBox</span><span class="p">(</span><span class="nl">width:</span> <span class="mi">8</span><span class="p">),</span>
    <span class="n">Expanded</span><span class="p">(</span>
      <span class="nl">flex:</span> <span class="mi">3</span><span class="p">,</span>
      <span class="nl">child:</span> <span class="n">Container</span><span class="p">(</span><span class="nl">height:</span> <span class="mi">50</span><span class="p">,</span> <span class="nl">color:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">orange</span><span class="p">),</span>
    <span class="p">),</span>  <span class="c1">// dapat 3/5 lebar</span>
  <span class="p">],</span>
<span class="p">)</span>
</code></pre></div></div>

<p>Total flex = 2 + 3 = 5. Hijau dapat 2/5, orange dapat 3/5.</p>

<hr />

<h2 id="4-flexible--mirip-expanded-tapi-bisa-ngeliat-dulu">4. Flexible — Mirip Expanded, Tapi Bisa “Ngeliat” Dulu</h2>

<p><code class="language-plaintext highlighter-rouge">Flexible</code> adalah kakak yang lebih santai dari <code class="language-plaintext highlighter-rouge">Expanded</code>. Bedanya:</p>

<table>
  <thead>
    <tr>
      <th>Fitur</th>
      <th>Expanded</th>
      <th>Flexible</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Default fit</strong></td>
      <td><code class="language-plaintext highlighter-rouge">FlexFit.tight</code></td>
      <td><code class="language-plaintext highlighter-rouge">FlexFit.loose</code></td>
    </tr>
    <tr>
      <td><strong>Memaksa isi</strong></td>
      <td>Widget child mengisi SEMUA ruang</td>
      <td>Child bebas lebih kecil dari ruang</td>
    </tr>
    <tr>
      <td><strong>Cocok untuk</strong></td>
      <td>Widget yang memang harus full-width</td>
      <td>Widget yang punya ukuran natural</td>
    </tr>
  </tbody>
</table>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Row</span><span class="p">(</span>
  <span class="nl">children:</span> <span class="p">[</span>
    <span class="n">Flexible</span><span class="p">(</span>
      <span class="nl">flex:</span> <span class="mi">1</span><span class="p">,</span>
      <span class="nl">child:</span> <span class="n">Container</span><span class="p">(</span>
        <span class="nl">height:</span> <span class="mi">50</span><span class="p">,</span>
        <span class="nl">color:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">purple</span><span class="p">,</span>
        <span class="nl">child:</span> <span class="kd">const</span> <span class="n">Text</span><span class="p">(</span><span class="s">'Aku Flexible — tidak harus full'</span><span class="p">),</span>
      <span class="p">),</span>
    <span class="p">),</span>
    <span class="kd">const</span> <span class="n">SizedBox</span><span class="p">(</span><span class="nl">width:</span> <span class="mi">8</span><span class="p">),</span>
    <span class="n">Expanded</span><span class="p">(</span>
      <span class="nl">flex:</span> <span class="mi">1</span><span class="p">,</span>
      <span class="nl">child:</span> <span class="n">Container</span><span class="p">(</span>
        <span class="nl">height:</span> <span class="mi">50</span><span class="p">,</span>
        <span class="nl">color:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">teal</span><span class="p">,</span>
        <span class="nl">child:</span> <span class="kd">const</span> <span class="n">Text</span><span class="p">(</span><span class="s">'Aku Expanded — selalu full'</span><span class="p">),</span>
      <span class="p">),</span>
    <span class="p">),</span>
  <span class="p">],</span>
<span class="p">)</span>
</code></pre></div></div>

<h3 id="kapan-pakai-flexible-vs-expanded">Kapan Pakai Flexible vs Expanded?</h3>

<ul>
  <li><strong>Expanded</strong>: Card, button, container yang harus memenuhi sisa ruang</li>
  <li><strong>Flexible</strong>: Text panjang yang boleh wrap, chip/tag yang ukurannya natural</li>
  <li>Konversi mudah: <code class="language-plaintext highlighter-rouge">Expanded()</code> sebenarnya sama dengan <code class="language-plaintext highlighter-rouge">Flexible(fit: FlexFit.tight)</code></li>
</ul>

<hr />

<h2 id="5-studi-kasus-card-profile-responsif">5. Studi Kasus: Card Profile Responsif</h2>

<p>Mari gabungkan semuanya dalam satu contoh nyata. Buat card profile yang:</p>
<ul>
  <li>Ada avatar bulat di kiri</li>
  <li>Nama dan bio di kanan</li>
  <li>Responsif di berbagai ukuran layar</li>
</ul>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="s">'package:flutter/material.dart'</span><span class="o">;</span>

<span class="kd">class</span> <span class="nc">ProfileCard</span> <span class="kd">extends</span> <span class="n">StatelessWidget</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="n">ProfileCard</span><span class="p">({</span><span class="k">super</span><span class="o">.</span><span class="na">key</span><span class="p">});</span>

  <span class="nd">@override</span>
  <span class="n">Widget</span> <span class="n">build</span><span class="p">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span> <span class="n">Card</span><span class="p">(</span>
      <span class="nl">margin:</span> <span class="kd">const</span> <span class="n">EdgeInsets</span><span class="o">.</span><span class="na">all</span><span class="p">(</span><span class="mi">16</span><span class="p">),</span>          <span class="c1">// margin luar</span>
      <span class="nl">child:</span> <span class="n">Padding</span><span class="p">(</span>
        <span class="nl">padding:</span> <span class="kd">const</span> <span class="n">EdgeInsets</span><span class="o">.</span><span class="na">all</span><span class="p">(</span><span class="mi">16</span><span class="p">),</span>        <span class="c1">// padding dalam</span>
        <span class="nl">child:</span> <span class="n">Row</span><span class="p">(</span>
          <span class="nl">crossAxisAlignment:</span> <span class="n">CrossAxisAlignment</span><span class="o">.</span><span class="na">start</span><span class="p">,</span>
          <span class="nl">children:</span> <span class="p">[</span>
            <span class="c1">// Avatar — ukuran fix 60x60</span>
            <span class="kd">const</span> <span class="n">SizedBox</span><span class="p">(</span>
              <span class="nl">width:</span> <span class="mi">60</span><span class="p">,</span>
              <span class="nl">height:</span> <span class="mi">60</span><span class="p">,</span>
              <span class="nl">child:</span> <span class="n">CircleAvatar</span><span class="p">(</span>
                <span class="nl">radius:</span> <span class="mi">30</span><span class="p">,</span>
                <span class="nl">backgroundImage:</span> <span class="n">NetworkImage</span><span class="p">(</span><span class="s">'https://i.pravatar.cc/150'</span><span class="p">),</span>
              <span class="p">),</span>
            <span class="p">),</span>
            <span class="kd">const</span> <span class="n">SizedBox</span><span class="p">(</span><span class="nl">width:</span> <span class="mi">16</span><span class="p">),</span>             <span class="c1">// spacer</span>
            <span class="c1">// Info profile — ambil sisa lebar</span>
            <span class="n">Expanded</span><span class="p">(</span>
              <span class="nl">child:</span> <span class="n">Column</span><span class="p">(</span>
                <span class="nl">crossAxisAlignment:</span> <span class="n">CrossAxisAlignment</span><span class="o">.</span><span class="na">start</span><span class="p">,</span>
                <span class="nl">children:</span> <span class="p">[</span>
                  <span class="kd">const</span> <span class="n">Text</span><span class="p">(</span>
                    <span class="s">'Ahmad Fathan'</span><span class="p">,</span>
                    <span class="nl">style:</span> <span class="n">TextStyle</span><span class="p">(</span>
                      <span class="nl">fontSize:</span> <span class="mi">18</span><span class="p">,</span>
                      <span class="nl">fontWeight:</span> <span class="n">FontWeight</span><span class="o">.</span><span class="na">bold</span><span class="p">,</span>
                    <span class="p">),</span>
                  <span class="p">),</span>
                  <span class="kd">const</span> <span class="n">SizedBox</span><span class="p">(</span><span class="nl">height:</span> <span class="mi">4</span><span class="p">),</span>
                  <span class="n">Text</span><span class="p">(</span>
                    <span class="s">'Senior Flutter Developer • Iya, bikin app dari nol sampai Play Store cuma modal ngopi.'</span><span class="p">,</span>
                    <span class="nl">style:</span> <span class="n">TextStyle</span><span class="p">(</span>
                      <span class="nl">fontSize:</span> <span class="mi">14</span><span class="p">,</span>
                      <span class="nl">color:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">grey</span><span class="p">[</span><span class="mi">600</span><span class="p">],</span>
                    <span class="p">),</span>
                  <span class="p">),</span>
                <span class="p">],</span>
              <span class="p">),</span>
            <span class="p">),</span>
          <span class="p">],</span>
        <span class="p">),</span>
      <span class="p">),</span>
    <span class="p">);</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Jalankan:</p>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="s">'package:flutter/material.dart'</span><span class="o">;</span>

<span class="kt">void</span> <span class="nf">main</span><span class="p">()</span> <span class="o">=</span><span class="p">&gt;</span> <span class="n">runApp</span><span class="p">(</span><span class="kd">const</span> <span class="n">MaterialApp</span><span class="p">(</span><span class="nl">home:</span> <span class="n">Scaffold</span><span class="p">(</span><span class="nl">body:</span> <span class="n">ProfileCard</span><span class="p">())));</span>
</code></pre></div></div>

<p>Hasilnya: avatar 60×60 bulat di kiri, teks fleksibel di kanan. Kalau layar kecil, teks otomatis wrap — berkat <code class="language-plaintext highlighter-rouge">Expanded</code> yang membatasi lebar maksimalnya.</p>

<hr />

<h2 id="6-cheat-sheet-cepat">6. Cheat Sheet Cepat</h2>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Pengen jarak antar widget?      → SizedBox(width/height: X)
Pengen padding di dalam?        → Padding(edgeInsets: ...)
Pengen margin di luar?          → Container(margin: ...)
Pengen isi sisa ruang penuh?    → Expanded(flex: ...)
Pengen isi sisa ruang fleksibel? → Flexible(flex: ...)
Pengen ukuran fix?              → SizedBox(width: X, height: Y)
Pengen ukuran fix + styling?    → Container(width: X, height: Y, decoration: ...)
</code></pre></div></div>

<hr />

<h2 id="7-common-mistakes-jangan-dilakukan">7. Common Mistakes (Jangan Dilakukan!)</h2>

<h3 id="-expanded-di-luar-rowcolumnflex">❌ <code class="language-plaintext highlighter-rouge">Expanded</code> di luar <code class="language-plaintext highlighter-rouge">Row</code>/<code class="language-plaintext highlighter-rouge">Column</code>/<code class="language-plaintext highlighter-rouge">Flex</code></h3>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// ERROR: Expanded harus child langsung dari Flex widget</span>
<span class="n">Scaffold</span><span class="p">(</span>
  <span class="nl">body:</span> <span class="n">Expanded</span><span class="p">(</span><span class="nl">child:</span> <span class="p">...),</span>  <span class="c1">// ❌ Tidak bisa!</span>
<span class="p">)</span>
</code></pre></div></div>

<h3 id="-lupa-crossaxisalignment-saat-pakai-expanded">❌ Lupa <code class="language-plaintext highlighter-rouge">crossAxisAlignment</code> saat pakai <code class="language-plaintext highlighter-rouge">Expanded</code></h3>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Row</span><span class="p">(</span>
  <span class="nl">children:</span> <span class="p">[</span>
    <span class="n">SizedBox</span><span class="p">(</span><span class="nl">width:</span> <span class="mi">60</span><span class="p">,</span> <span class="nl">height:</span> <span class="mi">60</span><span class="p">,</span> <span class="nl">child:</span> <span class="p">...),</span>
    <span class="n">Expanded</span><span class="p">(</span>
      <span class="nl">child:</span> <span class="n">Column</span><span class="p">(...),</span>   <span class="c1">// kalau Row, cross axis = vertical</span>
    <span class="p">),</span>
  <span class="p">],</span>
<span class="p">)</span>
<span class="c1">// Tambahkan crossAxisAlignment: CrossAxisAlignment.start pada Row</span>
<span class="c1">// supaya avatar tidak stretch!</span>
</code></pre></div></div>

<h3 id="-container-banyak-properti-untuk-hal-simpel">❌ <code class="language-plaintext highlighter-rouge">Container</code> banyak properti untuk hal simpel</h3>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// ❌ Berat</span>
<span class="n">Container</span><span class="p">(</span>
  <span class="nl">margin:</span> <span class="kd">const</span> <span class="n">EdgeInsets</span><span class="o">.</span><span class="na">only</span><span class="p">(</span><span class="nl">top:</span> <span class="mi">12</span><span class="p">),</span>
  <span class="nl">child:</span> <span class="kd">const</span> <span class="n">Text</span><span class="p">(</span><span class="s">'Halo'</span><span class="p">),</span>
<span class="p">)</span>

<span class="c1">// ✅ Ringan</span>
<span class="n">Padding</span><span class="p">(</span>
  <span class="nl">padding:</span> <span class="kd">const</span> <span class="n">EdgeInsets</span><span class="o">.</span><span class="na">only</span><span class="p">(</span><span class="nl">top:</span> <span class="mi">12</span><span class="p">),</span>
  <span class="nl">child:</span> <span class="kd">const</span> <span class="n">Text</span><span class="p">(</span><span class="s">'Halo'</span><span class="p">),</span>
<span class="p">)</span>
</code></pre></div></div>

<hr />

<h2 id="8-latihan-mandiri">8. Latihan Mandiri</h2>

<p>Coba bikin layout berikut sendiri:</p>

<ol>
  <li><strong>Card produk</strong>: Gambar produk di atas (height fix 200), judul + harga + rating di bawah (pakai Padding + SizedBox + Expanded)</li>
  <li><strong>Bar navigasi bawah buatan</strong>: Empat icon dalam Row, masing-masing pakai Expanded(flex: 1), biar lebar otomatis rata</li>
  <li><strong>Chat bubble</strong>: Pesan kiri (flexible, align kiri) dan pesan kanan (flexible, align kanan) dalam satu Column</li>
</ol>

<hr />

<h2 id="penutup">Penutup</h2>

<p>Lima widget ini — <code class="language-plaintext highlighter-rouge">Padding</code>, <code class="language-plaintext highlighter-rouge">SizedBox</code>, <code class="language-plaintext highlighter-rouge">Expanded</code>, <code class="language-plaintext highlighter-rouge">Flexible</code>, dan <code class="language-plaintext highlighter-rouge">Container(margin)</code> — adalah 90% dari pekerjaan layouting kamu sehari-hari. Begitu paham kombinasi ketiganya, bikin UI kompleks pun rasanya kayak main lego: tinggal susun, atur jarak, atur fleksibilitas, beres.</p>

<p>Di artikel berikutnya kita akan naik level: <strong>ListView &amp; GridView</strong> — bikin list konten yang bisa di-scroll, mulai dari list kontak, galeri foto, sampai feed berita.</p>

<p>Coba sendiri! Share hasil latihan kamu ke sosial media dan tag <strong>@ahsai001</strong> 🚀</p>]]></content><author><name></name></author><category term="flutter" /><category term="dart" /><category term="layouting" /><category term="padding" /><category term="margin" /><category term="sizedbox" /><category term="expanded" /><category term="flexible" /><category term="tutorial" /><summary type="html"><![CDATA[Kalau widget adalah bata, layouting adalah cara kita menyusun bata itu jadi rumah yang rapi. Di Flutter, urusan tata letak itu hampir semuanya pakai widget juga — dan justru di situlah kekuatannya. Nggak ada XML layout terpisah, nggak ada constraint-based layout yang bikin pusing. Semua adalah widget, semua composable.]]></summary></entry><entry><title type="html">Flutter #5: StatelessWidget vs StatefulWidget &amp;amp; setState</title><link href="https://ahsai.my.id/blog/2026/08/07/flutter-5-statelesswidget-vs-statefulwidget-setstate/" rel="alternate" type="text/html" title="Flutter #5: StatelessWidget vs StatefulWidget &amp;amp; setState" /><published>2026-08-07T00:00:00+00:00</published><updated>2026-08-07T00:00:00+00:00</updated><id>https://ahsai.my.id/blog/2026/08/07/flutter-5-statelesswidget-vs-statefulwidget-setstate</id><content type="html" xml:base="https://ahsai.my.id/blog/2026/08/07/flutter-5-statelesswidget-vs-statefulwidget-setstate/"><![CDATA[<p>Halo, teman-teman! Setelah kemarin kita ngulik widget dasar — <code class="language-plaintext highlighter-rouge">Text</code>, <code class="language-plaintext highlighter-rouge">Container</code>, <code class="language-plaintext highlighter-rouge">Row</code>, <code class="language-plaintext highlighter-rouge">Column</code> — sekarang saatnya masuk ke konsep yang bikin aplikasi Flutter jadi <strong>hidup</strong>: State! Kamu pasti pernah liat tombol yang berubah warna pas diklik, counter yang naik angkanya, atau form yang nampilin error pas input kosong. Semua itu berkat <strong>State Management</strong>. Dan pondasi pertamanya: <code class="language-plaintext highlighter-rouge">StatelessWidget</code> vs <code class="language-plaintext highlighter-rouge">StatefulWidget</code>.</p>

<hr />

<h2 id="apa-itu-state">Apa Itu “State”?</h2>

<p>Sederhananya, <strong>state</strong> adalah “keadaan saat ini” dari sebuah widget. Bayangin kamu punya lampu — state-nya bisa <code class="language-plaintext highlighter-rouge">hidup</code> atau <code class="language-plaintext highlighter-rouge">mati</code>. Begitu juga di Flutter:</p>

<ul>
  <li>Teks yang berubah setelah user ngetik → state</li>
  <li>Counter yang bertambah setelah tombol ditekan → state</li>
  <li>Checkbox yang dicentang → state</li>
  <li>Halaman yang berpindah → state</li>
</ul>

<p>Nah, Flutter membagi widget menjadi dua jenis berdasarkan apakah mereka punya state yang bisa berubah:</p>

<table>
  <thead>
    <tr>
      <th>Tipe Widget</th>
      <th>State Berubah?</th>
      <th>Contoh</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">StatelessWidget</code></td>
      <td>❌ Tidak</td>
      <td>Text, Icon, Image (statis)</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">StatefulWidget</code></td>
      <td>✅ Ya</td>
      <td>Checkbox, Slider, TextField, Counter</td>
    </tr>
  </tbody>
</table>

<hr />

<h2 id="statelesswidget--widget-yang-beku">StatelessWidget — Widget yang “Beku”</h2>

<p><code class="language-plaintext highlighter-rouge">StatelessWidget</code> adalah widget yang <strong>gak bisa berubah</strong> setelah dibuat. Begitu dia di-build, semua propertinya final — kayak foto yang sudah dijepret.</p>

<p><strong>Kapan pakai StatelessWidget?</strong> Kalau UI kamu cuma bergantung pada data yang dikasih dari luar dan gak akan berubah sendiri.</p>

<p>Contoh sederhana: widget ucapan selamat datang.</p>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="s">'package:flutter/material.dart'</span><span class="o">;</span>

<span class="kd">class</span> <span class="nc">WelcomeCard</span> <span class="kd">extends</span> <span class="n">StatelessWidget</span> <span class="p">{</span>
  <span class="kd">final</span> <span class="kt">String</span> <span class="n">username</span><span class="p">;</span>

  <span class="kd">const</span> <span class="n">WelcomeCard</span><span class="p">({</span><span class="n">Key</span><span class="o">?</span> <span class="n">key</span><span class="p">,</span> <span class="kd">required</span> <span class="k">this</span><span class="o">.</span><span class="na">username</span><span class="p">})</span> <span class="o">:</span> <span class="k">super</span><span class="p">(</span><span class="nl">key:</span> <span class="n">key</span><span class="p">);</span>

  <span class="nd">@override</span>
  <span class="n">Widget</span> <span class="n">build</span><span class="p">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span> <span class="n">Container</span><span class="p">(</span>
      <span class="nl">padding:</span> <span class="kd">const</span> <span class="n">EdgeInsets</span><span class="o">.</span><span class="na">all</span><span class="p">(</span><span class="mi">20</span><span class="p">),</span>
      <span class="nl">margin:</span> <span class="kd">const</span> <span class="n">EdgeInsets</span><span class="o">.</span><span class="na">all</span><span class="p">(</span><span class="mi">16</span><span class="p">),</span>
      <span class="nl">decoration:</span> <span class="n">BoxDecoration</span><span class="p">(</span>
        <span class="nl">color:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">indigo</span><span class="o">.</span><span class="na">shade50</span><span class="p">,</span>
        <span class="nl">borderRadius:</span> <span class="n">BorderRadius</span><span class="o">.</span><span class="na">circular</span><span class="p">(</span><span class="mi">12</span><span class="p">),</span>
      <span class="p">),</span>
      <span class="nl">child:</span> <span class="n">Row</span><span class="p">(</span>
        <span class="nl">children:</span> <span class="p">[</span>
          <span class="n">Icon</span><span class="p">(</span><span class="n">Icons</span><span class="o">.</span><span class="na">waving_hand</span><span class="p">,</span> <span class="nl">color:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">amber</span><span class="o">.</span><span class="na">shade700</span><span class="p">,</span> <span class="nl">size:</span> <span class="mi">32</span><span class="p">),</span>
          <span class="kd">const</span> <span class="n">SizedBox</span><span class="p">(</span><span class="nl">width:</span> <span class="mi">12</span><span class="p">),</span>
          <span class="n">Text</span><span class="p">(</span>
            <span class="s">'Halo, </span><span class="si">$username</span><span class="s">! Selamat datang kembali.'</span><span class="p">,</span>
            <span class="nl">style:</span> <span class="kd">const</span> <span class="n">TextStyle</span><span class="p">(</span><span class="nl">fontSize:</span> <span class="mi">18</span><span class="p">),</span>
          <span class="p">),</span>
        <span class="p">],</span>
      <span class="p">),</span>
    <span class="p">);</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Widget ini nerima <code class="language-plaintext highlighter-rouge">username</code> dari constructor, lalu langsung render. Gak ada yang berubah sendiri. <code class="language-plaintext highlighter-rouge">build()</code> cuma dipanggil sekali saat widget dibuat.</p>

<p><strong>Ciri StatelessWidget:</strong></p>
<ul>
  <li>Cuma punya method <code class="language-plaintext highlighter-rouge">build()</code>.</li>
  <li>Semua data masuk lewat constructor, bersifat <code class="language-plaintext highlighter-rouge">final</code>.</li>
  <li>Ringan, performanya bagus — gak ada overhead state.</li>
</ul>

<hr />

<h2 id="statefulwidget--widget-yang-hidup">StatefulWidget — Widget yang “Hidup”</h2>

<p>Nah, kalau <code class="language-plaintext highlighter-rouge">StatefulWidget</code> ini widget yang <strong>state-nya bisa berubah seiring waktu</strong>. FLutter mengawinkan <code class="language-plaintext highlighter-rouge">StatefulWidget</code> dengan class pasangannya: <code class="language-plaintext highlighter-rouge">State</code>. Jadi ada dua class:</p>

<ol>
  <li><strong>StatefulWidget</strong> — immutable, cuma bungkus, nerima data dari luar.</li>
  <li><strong>State&lt;T&gt;</strong> — mutable, tempat logic dan data yang berubah berada.</li>
</ol>

<p>Contoh paling klasik: <strong>counter app</strong>.</p>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="s">'package:flutter/material.dart'</span><span class="o">;</span>

<span class="kd">class</span> <span class="nc">CounterScreen</span> <span class="kd">extends</span> <span class="n">StatefulWidget</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="n">CounterScreen</span><span class="p">({</span><span class="n">Key</span><span class="o">?</span> <span class="n">key</span><span class="p">})</span> <span class="o">:</span> <span class="k">super</span><span class="p">(</span><span class="nl">key:</span> <span class="n">key</span><span class="p">);</span>

  <span class="nd">@override</span>
  <span class="n">State</span><span class="p">&lt;</span><span class="n">CounterScreen</span><span class="p">&gt;</span> <span class="n">createState</span><span class="p">()</span> <span class="o">=</span><span class="p">&gt;</span> <span class="n">_CounterScreenState</span><span class="p">();</span>
<span class="p">}</span>

<span class="kd">class</span> <span class="nc">_CounterScreenState</span> <span class="kd">extends</span> <span class="n">State</span><span class="p">&lt;</span><span class="n">CounterScreen</span><span class="p">&gt;</span> <span class="p">{</span>
  <span class="kt">int</span> <span class="n">_counter</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>

  <span class="kt">void</span> <span class="n">_increment</span><span class="p">()</span> <span class="p">{</span>
    <span class="n">setState</span><span class="p">(()</span> <span class="p">{</span>
      <span class="n">_counter</span><span class="o">++</span><span class="p">;</span>
    <span class="p">});</span>
  <span class="p">}</span>

  <span class="kt">void</span> <span class="n">_decrement</span><span class="p">()</span> <span class="p">{</span>
    <span class="n">setState</span><span class="p">(()</span> <span class="p">{</span>
      <span class="k">if</span> <span class="p">(</span><span class="n">_counter</span> <span class="p">&gt;</span> <span class="mi">0</span><span class="p">)</span> <span class="n">_counter</span><span class="o">--</span><span class="p">;</span>
    <span class="p">});</span>
  <span class="p">}</span>

  <span class="kt">void</span> <span class="n">_reset</span><span class="p">()</span> <span class="p">{</span>
    <span class="n">setState</span><span class="p">(()</span> <span class="p">{</span>
      <span class="n">_counter</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
    <span class="p">});</span>
  <span class="p">}</span>

  <span class="nd">@override</span>
  <span class="n">Widget</span> <span class="n">build</span><span class="p">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span> <span class="n">Scaffold</span><span class="p">(</span>
      <span class="nl">appBar:</span> <span class="n">AppBar</span><span class="p">(</span>
        <span class="nl">title:</span> <span class="kd">const</span> <span class="n">Text</span><span class="p">(</span><span class="s">'Belajar StatefulWidget'</span><span class="p">),</span>
        <span class="nl">centerTitle:</span> <span class="kc">true</span><span class="p">,</span>
      <span class="p">),</span>
      <span class="nl">body:</span> <span class="n">Center</span><span class="p">(</span>
        <span class="nl">child:</span> <span class="n">Column</span><span class="p">(</span>
          <span class="nl">mainAxisAlignment:</span> <span class="n">MainAxisAlignment</span><span class="o">.</span><span class="na">center</span><span class="p">,</span>
          <span class="nl">children:</span> <span class="p">[</span>
            <span class="n">Text</span><span class="p">(</span>
              <span class="s">'Kamu sudah klik sebanyak:'</span><span class="p">,</span>
              <span class="nl">style:</span> <span class="n">TextStyle</span><span class="p">(</span><span class="nl">fontSize:</span> <span class="mi">18</span><span class="p">,</span> <span class="nl">color:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">grey</span><span class="o">.</span><span class="na">shade600</span><span class="p">),</span>
            <span class="p">),</span>
            <span class="kd">const</span> <span class="n">SizedBox</span><span class="p">(</span><span class="nl">height:</span> <span class="mi">12</span><span class="p">),</span>
            <span class="n">Text</span><span class="p">(</span>
              <span class="s">'</span><span class="si">$_counter</span><span class="s">'</span><span class="p">,</span>
              <span class="nl">style:</span> <span class="kd">const</span> <span class="n">TextStyle</span><span class="p">(</span>
                <span class="nl">fontSize:</span> <span class="mi">72</span><span class="p">,</span>
                <span class="nl">fontWeight:</span> <span class="n">FontWeight</span><span class="o">.</span><span class="na">bold</span><span class="p">,</span>
                <span class="nl">color:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">indigo</span><span class="p">,</span>
              <span class="p">),</span>
            <span class="p">),</span>
            <span class="kd">const</span> <span class="n">SizedBox</span><span class="p">(</span><span class="nl">height:</span> <span class="mi">32</span><span class="p">),</span>
            <span class="n">Row</span><span class="p">(</span>
              <span class="nl">mainAxisAlignment:</span> <span class="n">MainAxisAlignment</span><span class="o">.</span><span class="na">center</span><span class="p">,</span>
              <span class="nl">children:</span> <span class="p">[</span>
                <span class="n">ElevatedButton</span><span class="o">.</span><span class="na">icon</span><span class="p">(</span>
                  <span class="nl">onPressed:</span> <span class="n">_decrement</span><span class="p">,</span>
                  <span class="nl">icon:</span> <span class="kd">const</span> <span class="n">Icon</span><span class="p">(</span><span class="n">Icons</span><span class="o">.</span><span class="na">remove</span><span class="p">),</span>
                  <span class="nl">label:</span> <span class="kd">const</span> <span class="n">Text</span><span class="p">(</span><span class="s">'Kurang'</span><span class="p">),</span>
                <span class="p">),</span>
                <span class="kd">const</span> <span class="n">SizedBox</span><span class="p">(</span><span class="nl">width:</span> <span class="mi">16</span><span class="p">),</span>
                <span class="n">ElevatedButton</span><span class="o">.</span><span class="na">icon</span><span class="p">(</span>
                  <span class="nl">onPressed:</span> <span class="n">_increment</span><span class="p">,</span>
                  <span class="nl">icon:</span> <span class="kd">const</span> <span class="n">Icon</span><span class="p">(</span><span class="n">Icons</span><span class="o">.</span><span class="na">add</span><span class="p">),</span>
                  <span class="nl">label:</span> <span class="kd">const</span> <span class="n">Text</span><span class="p">(</span><span class="s">'Tambah'</span><span class="p">),</span>
                <span class="p">),</span>
              <span class="p">],</span>
            <span class="p">),</span>
            <span class="kd">const</span> <span class="n">SizedBox</span><span class="p">(</span><span class="nl">height:</span> <span class="mi">12</span><span class="p">),</span>
            <span class="n">TextButton</span><span class="p">(</span>
              <span class="nl">onPressed:</span> <span class="n">_counter</span> <span class="p">&gt;</span> <span class="mi">0</span> <span class="o">?</span> <span class="n">_reset</span> <span class="o">:</span> <span class="kc">null</span><span class="p">,</span>
              <span class="nl">child:</span> <span class="kd">const</span> <span class="n">Text</span><span class="p">(</span><span class="s">'Reset'</span><span class="p">),</span>
            <span class="p">),</span>
          <span class="p">],</span>
        <span class="p">),</span>
      <span class="p">),</span>
    <span class="p">);</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="magic-nya-setstate">Magic-nya <code class="language-plaintext highlighter-rouge">setState()</code></h3>

<p><code class="language-plaintext highlighter-rouge">setState()</code> adalah rahasia di balik kenapa UI Flutter bisa berubah. Begitu kamu panggil:</p>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">setState</span><span class="p">(()</span> <span class="p">{</span>
  <span class="n">_counter</span><span class="o">++</span><span class="p">;</span> <span class="c1">// ubah data di sini</span>
<span class="p">});</span>
</code></pre></div></div>

<p>Yang terjadi:</p>
<ol>
  <li>Flutter menjalankan callback di dalam <code class="language-plaintext highlighter-rouge">setState()</code> — jadi <code class="language-plaintext highlighter-rouge">_counter</code> bertambah.</li>
  <li>Flutter otomatis memanggil ulang <code class="language-plaintext highlighter-rouge">build()</code>.</li>
  <li>UI di-rebuild hanya dengan data terbaru.</li>
  <li>Hasilnya: layar menampilkan angka counter yang baru!</li>
</ol>

<p><strong>Tanpa <code class="language-plaintext highlighter-rouge">setState()</code>,</strong> meskipun nilai <code class="language-plaintext highlighter-rouge">_counter</code> berubah di memori, Flutter gak akan tahu kalau UI perlu direfresh. Angka tetap seperti semula.</p>

<blockquote>
  <p>⚠️ <strong>Jangan taruh logic berat di dalam <code class="language-plaintext highlighter-rouge">setState()</code>!</strong> Isinya sebaiknya cuma assignment variabel ringan. Kalau kamu manggil API atau baca file di dalam <code class="language-plaintext highlighter-rouge">setState()</code>, UI bakal freeze.</p>
</blockquote>

<hr />

<h2 id="lifecycle-statefulwidget-penting">Lifecycle StatefulWidget (Penting!)</h2>

<p>StatefulWidget punya siklus hidup yang perlu kamu kenali sejak awal:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>createState()    →  initState()   →  build()
                                      ↓
                              setState() → build()
                                      ↓
                              dispose()
</code></pre></div></div>

<table>
  <thead>
    <tr>
      <th>Method</th>
      <th>Kapan Dipanggil</th>
      <th>Gunanya</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">initState()</code></td>
      <td>Sekali, sebelum <code class="language-plaintext highlighter-rouge">build()</code> pertama</td>
      <td>Inisialisasi data, listener, controller</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">build()</code></td>
      <td>Setiap kali widget dirender</td>
      <td>Membangun UI</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">setState()</code></td>
      <td>Kamu panggil manual saat data berubah</td>
      <td>Trigger rebuild UI</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">dispose()</code></td>
      <td>Saat widget dihancurkan dari widget tree</td>
      <td>Bersihin controller, listener, subscription</td>
    </tr>
  </tbody>
</table>

<p>Contoh dengan <code class="language-plaintext highlighter-rouge">initState</code> dan <code class="language-plaintext highlighter-rouge">dispose</code>:</p>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">TimerWidget</span> <span class="kd">extends</span> <span class="n">StatefulWidget</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="n">TimerWidget</span><span class="p">({</span><span class="n">Key</span><span class="o">?</span> <span class="n">key</span><span class="p">})</span> <span class="o">:</span> <span class="k">super</span><span class="p">(</span><span class="nl">key:</span> <span class="n">key</span><span class="p">);</span>

  <span class="nd">@override</span>
  <span class="n">State</span><span class="p">&lt;</span><span class="n">TimerWidget</span><span class="p">&gt;</span> <span class="n">createState</span><span class="p">()</span> <span class="o">=</span><span class="p">&gt;</span> <span class="n">_TimerWidgetState</span><span class="p">();</span>
<span class="p">}</span>

<span class="kd">class</span> <span class="nc">_TimerWidgetState</span> <span class="kd">extends</span> <span class="n">State</span><span class="p">&lt;</span><span class="n">TimerWidget</span><span class="p">&gt;</span> <span class="p">{</span>
  <span class="kt">int</span> <span class="n">_seconds</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>

  <span class="nd">@override</span>
  <span class="kt">void</span> <span class="n">initState</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">super</span><span class="o">.</span><span class="na">initState</span><span class="p">();</span>
    <span class="c1">// Mulai timer pas widget pertama kali dibuat</span>
    <span class="n">Future</span><span class="o">.</span><span class="na">delayed</span><span class="p">(</span><span class="kd">const</span> <span class="n">Duration</span><span class="p">(</span><span class="nl">seconds:</span> <span class="mi">1</span><span class="p">),</span> <span class="n">_tick</span><span class="p">);</span>
  <span class="p">}</span>

  <span class="kt">void</span> <span class="n">_tick</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">mounted</span><span class="p">)</span> <span class="k">return</span><span class="p">;</span> <span class="c1">// cek apakah widget masih ada</span>
    <span class="n">setState</span><span class="p">(()</span> <span class="o">=</span><span class="p">&gt;</span> <span class="n">_seconds</span><span class="o">++</span><span class="p">);</span>
    <span class="n">Future</span><span class="o">.</span><span class="na">delayed</span><span class="p">(</span><span class="kd">const</span> <span class="n">Duration</span><span class="p">(</span><span class="nl">seconds:</span> <span class="mi">1</span><span class="p">),</span> <span class="n">_tick</span><span class="p">);</span>
  <span class="p">}</span>

  <span class="nd">@override</span>
  <span class="kt">void</span> <span class="n">dispose</span><span class="p">()</span> <span class="p">{</span>
    <span class="c1">// Gak perlu apa-apa di sini karena pakai Future, bukan Timer</span>
    <span class="c1">// Tapi kalau pakai Timer.periodic, wajib cancel di sini!</span>
    <span class="k">super</span><span class="o">.</span><span class="na">dispose</span><span class="p">();</span>
  <span class="p">}</span>

  <span class="nd">@override</span>
  <span class="n">Widget</span> <span class="n">build</span><span class="p">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span> <span class="n">Text</span><span class="p">(</span><span class="s">'Waktu: </span><span class="si">$_seconds</span><span class="s"> detik'</span><span class="p">);</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Pro tip: selalu panggil <code class="language-plaintext highlighter-rouge">super.initState()</code> di awal dan <code class="language-plaintext highlighter-rouge">super.dispose()</code> di akhir.</p>

<hr />

<h2 id="kapan-pakai-yang-mana">Kapan Pakai yang Mana?</h2>

<p><strong>Gunakan StatelessWidget kalau:</strong></p>
<ul>
  <li>UI cuma menampilkan data yang gak berubah (label, logo, header statis)</li>
  <li>Widget cuma nge-wrap child tanpa logic internal</li>
</ul>

<p><strong>Gunakan StatefulWidget kalau:</strong></p>
<ul>
  <li>Ada data yang bisa berubah karena interaksi user (counter, form, checkbox)</li>
  <li>Ada animasi, timer, atau efek berbasis waktu</li>
  <li>Widget punya lifecycle event yang perlu di-handle (<code class="language-plaintext highlighter-rouge">initState</code>, <code class="language-plaintext highlighter-rouge">dispose</code>)</li>
</ul>

<hr />

<h2 id="tips-biar-gak-salah-paham">Tips Biar Gak Salah Paham</h2>

<ol>
  <li><strong>Stateless ≠ gak bisa update sama sekali.</strong> Kalau parent-nya rebuild (misal lewat <code class="language-plaintext highlighter-rouge">setState</code> di parent), <code class="language-plaintext highlighter-rouge">StatelessWidget</code> juga ikut dirender ulang dengan data baru.</li>
  <li><strong>Minimalkan StatefulWidget.</strong> Makin banyak state, makin kompleks debugging-nya. Kalau bisa pakai <code class="language-plaintext highlighter-rouge">Stateless</code>, pakai aja.</li>
  <li><strong><code class="language-plaintext highlighter-rouge">setState()</code> bukan satu-satunya cara.</strong> Nanti di artikel lanjutan kita bakal bahas Provider, BLoC, GetX — cara yang lebih scalable buat state management.</li>
</ol>

<hr />

<h2 id="kesimpulan">Kesimpulan</h2>

<p>Hari ini kita belajar:</p>
<ul>
  <li><strong>State</strong> = data yang bisa berubah di widget</li>
  <li><strong>StatelessWidget</strong> = widget statis, gak bisa berubah sendiri</li>
  <li><strong>StatefulWidget</strong> = widget dinamis, state-nya bisa diubah lewat <code class="language-plaintext highlighter-rouge">setState()</code></li>
  <li><strong>setState()</strong> = cara Flutter untuk rebuild UI saat data berubah</li>
  <li><strong>Lifecycle</strong> = <code class="language-plaintext highlighter-rouge">initState</code> → <code class="language-plaintext highlighter-rouge">build</code> ↔ <code class="language-plaintext highlighter-rouge">setState</code> → <code class="language-plaintext highlighter-rouge">dispose</code></li>
</ul>

<p>Di artikel berikutnya, kita masuk ke <strong>Layouting</strong>: cara mengatur padding, margin, <code class="language-plaintext highlighter-rouge">SizedBox</code>, <code class="language-plaintext highlighter-rouge">Expanded</code>, dan <code class="language-plaintext highlighter-rouge">Flexible</code> supaya aplikasi kamu rapi di semua ukuran layar. Stay tuned!</p>

<hr />

<p>Coba sendiri counter app di atas, tambahin fitur kayak multiply atau auto-increment, terus share ke sosial media dan tag <a href="https://ahsai.my.id">@ahsai001</a>! Sampai jumpa besok. Happy coding! 🚀</p>]]></content><author><name></name></author><category term="flutter" /><category term="dart" /><category term="tutorial" /><category term="indonesia" /><category term="pemrograman" /><category term="state" /><category term="statefulwidget" /><summary type="html"><![CDATA[Halo, teman-teman! Setelah kemarin kita ngulik widget dasar — Text, Container, Row, Column — sekarang saatnya masuk ke konsep yang bikin aplikasi Flutter jadi hidup: State! Kamu pasti pernah liat tombol yang berubah warna pas diklik, counter yang naik angkanya, atau form yang nampilin error pas input kosong. Semua itu berkat State Management. Dan pondasi pertamanya: StatelessWidget vs StatefulWidget.]]></summary></entry><entry><title type="html">Flutter #4: Widget Dasar — Text, Container, Row, Column</title><link href="https://ahsai.my.id/blog/2026/08/06/flutter-4-widget-dasar-text-container-row-column/" rel="alternate" type="text/html" title="Flutter #4: Widget Dasar — Text, Container, Row, Column" /><published>2026-08-06T00:00:00+00:00</published><updated>2026-08-06T00:00:00+00:00</updated><id>https://ahsai.my.id/blog/2026/08/06/flutter-4-widget-dasar-text-container-row-column</id><content type="html" xml:base="https://ahsai.my.id/blog/2026/08/06/flutter-4-widget-dasar-text-container-row-column/"><![CDATA[<p>Halo, teman-teman! Setelah kita kenalan sama Flutter (#1), install SDK-nya (#2), dan paham struktur project (#3), sekarang saatnya masuk ke bagian paling seru: <strong>ngoding UI</strong>! Di Flutter, semuanya adalah widget. Mau bikin teks? Widget. Mau bikin kotak? Widget. Mau atur tata letak? Widget lagi. Jadi hari ini kita bahas empat widget paling fundamental yang bakal kamu pakai di setiap halaman: <code class="language-plaintext highlighter-rouge">Text</code>, <code class="language-plaintext highlighter-rouge">Container</code>, <code class="language-plaintext highlighter-rouge">Row</code>, dan <code class="language-plaintext highlighter-rouge">Column</code>.</p>

<hr />

<h2 id="widget--lego-blocks">Widget = Lego Blocks</h2>

<p>Bayangin Flutter kayak main Lego. Satu brick kecil itu <strong>widget</strong>. Kamu gabungin brick-brick itu buat bikin layar aplikasi. Semua yang kamu lihat di Flutter — tombol, teks, gambar, spasi, scroll — semuanya widget.</p>

<p>Flutter punya dua pendekatan UI:</p>
<ol>
  <li><strong>Imperatif</strong> — kayak Android XML: “bikin TextView, kasih warna, taruh di kiri atas.”</li>
  <li><strong>Deklaratif</strong> — Flutter style: “gambarin UI kayak gini <em>sekarang</em>, kalau state berubah ya gambar ulang.”</li>
</ol>

<p>Nah, kita langsung gaspol ke empat widget paling dasar.</p>

<hr />

<h2 id="1-text--bikin-tulisan">1. Text — Bikin Tulisan</h2>

<p>Widget paling sederhana: <code class="language-plaintext highlighter-rouge">Text()</code>. Cukup kasih string, langsung muncul di layar.</p>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="s">'package:flutter/material.dart'</span><span class="o">;</span>

<span class="kt">void</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
  <span class="n">runApp</span><span class="p">(</span><span class="n">MaterialApp</span><span class="p">(</span>
    <span class="nl">home:</span> <span class="n">Scaffold</span><span class="p">(</span>
      <span class="nl">body:</span> <span class="n">Center</span><span class="p">(</span>
        <span class="nl">child:</span> <span class="n">Text</span><span class="p">(</span><span class="s">'Halo, Flutter!'</span><span class="p">),</span>
      <span class="p">),</span>
    <span class="p">),</span>
  <span class="p">));</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Segampang itu. Tapi <code class="language-plaintext highlighter-rouge">Text</code> punya banyak styling:</p>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Text</span><span class="p">(</span>
  <span class="s">'Belajar Flutter itu menyenangkan!'</span><span class="p">,</span>
  <span class="nl">style:</span> <span class="n">TextStyle</span><span class="p">(</span>
    <span class="nl">fontSize:</span> <span class="mi">24</span><span class="p">,</span>
    <span class="nl">fontWeight:</span> <span class="n">FontWeight</span><span class="o">.</span><span class="na">bold</span><span class="p">,</span>
    <span class="nl">color:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">blue</span><span class="o">.</span><span class="na">shade700</span><span class="p">,</span>
    <span class="nl">fontStyle:</span> <span class="n">FontStyle</span><span class="o">.</span><span class="na">italic</span><span class="p">,</span>
    <span class="nl">letterSpacing:</span> <span class="mf">1.2</span><span class="p">,</span>
    <span class="nl">height:</span> <span class="mf">1.5</span><span class="p">,</span> <span class="c1">// line-height</span>
  <span class="p">),</span>
  <span class="nl">textAlign:</span> <span class="n">TextAlign</span><span class="o">.</span><span class="na">center</span><span class="p">,</span>
  <span class="nl">maxLines:</span> <span class="mi">2</span><span class="p">,</span>
  <span class="nl">overflow:</span> <span class="n">TextOverflow</span><span class="o">.</span><span class="na">ellipsis</span><span class="p">,</span>
<span class="p">)</span>
</code></pre></div></div>

<p><strong>Yang perlu kamu ingat:</strong></p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">style</code> pakai <code class="language-plaintext highlighter-rouge">TextStyle</code> — ini tempat semua properti font</li>
  <li><code class="language-plaintext highlighter-rouge">textAlign</code> buat rata kiri/kanan/tengah</li>
  <li><code class="language-plaintext highlighter-rouge">maxLines</code> + <code class="language-plaintext highlighter-rouge">overflow</code> berguna biar teks gak kepanjangan</li>
</ul>

<h3 id="text-direction--softwrap">Text Direction &amp; Softwrap</h3>

<p>Flutter otomatis <code class="language-plaintext highlighter-rouge">softwrap</code> teks yang panjang. Tapi kalau container-nya sempit dan gak ada scroll, teks bisa meluap (muncul garis kuning-hitam di debug mode). Solusinya: bungkus <code class="language-plaintext highlighter-rouge">Text</code> dengan <code class="language-plaintext highlighter-rouge">Expanded</code> atau <code class="language-plaintext highlighter-rouge">Flexible</code> kalau dia ada di dalam <code class="language-plaintext highlighter-rouge">Row</code>/<code class="language-plaintext highlighter-rouge">Column</code>.</p>

<hr />

<h2 id="2-container--kotak-serbaguna">2. Container — Kotak Serbaguna</h2>

<p>Kalau <code class="language-plaintext highlighter-rouge">Text</code> itu isi, <code class="language-plaintext highlighter-rouge">Container</code> itu <strong>bungkusnya</strong>. Dia kotak yang bisa kamu kasih warna, padding, margin, border, shadow — kayak <code class="language-plaintext highlighter-rouge">&lt;div&gt;</code>-nya Flutter.</p>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Container</span><span class="p">(</span>
  <span class="nl">width:</span> <span class="mi">200</span><span class="p">,</span>
  <span class="nl">height:</span> <span class="mi">150</span><span class="p">,</span>
  <span class="nl">padding:</span> <span class="n">EdgeInsets</span><span class="o">.</span><span class="na">all</span><span class="p">(</span><span class="mi">16</span><span class="p">),</span>
  <span class="nl">margin:</span> <span class="n">EdgeInsets</span><span class="o">.</span><span class="na">symmetric</span><span class="p">(</span><span class="nl">horizontal:</span> <span class="mi">20</span><span class="p">,</span> <span class="nl">vertical:</span> <span class="mi">10</span><span class="p">),</span>
  <span class="nl">decoration:</span> <span class="n">BoxDecoration</span><span class="p">(</span>
    <span class="nl">color:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">teal</span><span class="o">.</span><span class="na">shade100</span><span class="p">,</span>
    <span class="nl">borderRadius:</span> <span class="n">BorderRadius</span><span class="o">.</span><span class="na">circular</span><span class="p">(</span><span class="mi">12</span><span class="p">),</span>
    <span class="nl">border:</span> <span class="n">Border</span><span class="o">.</span><span class="na">all</span><span class="p">(</span><span class="nl">color:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">teal</span><span class="p">,</span> <span class="nl">width:</span> <span class="mi">2</span><span class="p">),</span>
    <span class="nl">boxShadow:</span> <span class="p">[</span>
      <span class="n">BoxShadow</span><span class="p">(</span>
        <span class="nl">color:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">teal</span><span class="o">.</span><span class="na">withOpacity</span><span class="p">(</span><span class="mf">0.3</span><span class="p">),</span>
        <span class="nl">blurRadius:</span> <span class="mi">8</span><span class="p">,</span>
        <span class="nl">offset:</span> <span class="n">Offset</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">4</span><span class="p">),</span>
      <span class="p">),</span>
    <span class="p">],</span>
  <span class="p">),</span>
  <span class="nl">child:</span> <span class="n">Text</span><span class="p">(</span><span class="s">'Ini dalam Container'</span><span class="p">,</span> <span class="nl">style:</span> <span class="n">TextStyle</span><span class="p">(</span><span class="nl">fontSize:</span> <span class="mi">18</span><span class="p">)),</span>
<span class="p">)</span>
</code></pre></div></div>

<p><strong>Container properties wajib tahu:</strong></p>

<table>
  <thead>
    <tr>
      <th>Property</th>
      <th>Fungsi</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">width</code> / <code class="language-plaintext highlighter-rouge">height</code></td>
      <td>Ukuran kotak</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">padding</code></td>
      <td>Jarak dari border ke child</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">margin</code></td>
      <td>Jarak dari luar ke border</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">decoration</code></td>
      <td>Warna, border, shadow (pakai <code class="language-plaintext highlighter-rouge">BoxDecoration</code>)</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">child</code></td>
      <td>Widget di dalamnya</td>
    </tr>
  </tbody>
</table>

<p><strong>⚠️ PENTING:</strong> Jangan pakai <code class="language-plaintext highlighter-rouge">color</code> bareng <code class="language-plaintext highlighter-rouge">decoration</code>! Kalau kamu pakai <code class="language-plaintext highlighter-rouge">decoration: BoxDecoration(...)</code>, properti <code class="language-plaintext highlighter-rouge">color</code> harus dimasukin ke <code class="language-plaintext highlighter-rouge">BoxDecoration</code>, bukan langsung ke <code class="language-plaintext highlighter-rouge">Container</code>. Kalau kamu tulis dua-duanya, Flutter error.</p>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// ❌ SALAH — bakal error</span>
<span class="n">Container</span><span class="p">(</span><span class="nl">color:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">red</span><span class="p">,</span> <span class="nl">decoration:</span> <span class="n">BoxDecoration</span><span class="p">(</span><span class="nl">color:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">blue</span><span class="p">))</span>

<span class="c1">// ✅ BENAR</span>
<span class="n">Container</span><span class="p">(</span><span class="nl">decoration:</span> <span class="n">BoxDecoration</span><span class="p">(</span><span class="nl">color:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">blue</span><span class="p">))</span>
</code></pre></div></div>

<hr />

<h2 id="3-row--column--atur-tata-letak">3. Row &amp; Column — Atur Tata Letak</h2>

<p>Inilah dua widget paling dipakai buat layouting. <strong>Row</strong> menyusun child horizontal (kiri ke kanan), <strong>Column</strong> menyusun vertikal (atas ke bawah). Mirip <code class="language-plaintext highlighter-rouge">flexbox</code> di CSS.</p>

<h3 id="row">Row</h3>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Row</span><span class="p">(</span>
  <span class="nl">mainAxisAlignment:</span> <span class="n">MainAxisAlignment</span><span class="o">.</span><span class="na">spaceEvenly</span><span class="p">,</span>
  <span class="nl">crossAxisAlignment:</span> <span class="n">CrossAxisAlignment</span><span class="o">.</span><span class="na">center</span><span class="p">,</span>
  <span class="nl">children:</span> <span class="p">[</span>
    <span class="n">Icon</span><span class="p">(</span><span class="n">Icons</span><span class="o">.</span><span class="na">star</span><span class="p">,</span> <span class="nl">color:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">amber</span><span class="p">,</span> <span class="nl">size:</span> <span class="mi">32</span><span class="p">),</span>
    <span class="n">Icon</span><span class="p">(</span><span class="n">Icons</span><span class="o">.</span><span class="na">favorite</span><span class="p">,</span> <span class="nl">color:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">red</span><span class="p">,</span> <span class="nl">size:</span> <span class="mi">32</span><span class="p">),</span>
    <span class="n">Icon</span><span class="p">(</span><span class="n">Icons</span><span class="o">.</span><span class="na">thumb_up</span><span class="p">,</span> <span class="nl">color:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">blue</span><span class="p">,</span> <span class="nl">size:</span> <span class="mi">32</span><span class="p">),</span>
    <span class="n">Icon</span><span class="p">(</span><span class="n">Icons</span><span class="o">.</span><span class="na">share</span><span class="p">,</span> <span class="nl">color:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">green</span><span class="p">,</span> <span class="nl">size:</span> <span class="mi">32</span><span class="p">),</span>
  <span class="p">],</span>
<span class="p">)</span>
</code></pre></div></div>

<h3 id="column">Column</h3>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Column</span><span class="p">(</span>
  <span class="nl">mainAxisAlignment:</span> <span class="n">MainAxisAlignment</span><span class="o">.</span><span class="na">center</span><span class="p">,</span>
  <span class="nl">crossAxisAlignment:</span> <span class="n">CrossAxisAlignment</span><span class="o">.</span><span class="na">start</span><span class="p">,</span>
  <span class="nl">children:</span> <span class="p">[</span>
    <span class="n">Text</span><span class="p">(</span><span class="s">'Nama: Ahsai'</span><span class="p">,</span> <span class="nl">style:</span> <span class="n">TextStyle</span><span class="p">(</span><span class="nl">fontSize:</span> <span class="mi">20</span><span class="p">)),</span>
    <span class="n">SizedBox</span><span class="p">(</span><span class="nl">height:</span> <span class="mi">8</span><span class="p">),</span>
    <span class="n">Text</span><span class="p">(</span><span class="s">'Role: Flutter Developer'</span><span class="p">,</span> <span class="nl">style:</span> <span class="n">TextStyle</span><span class="p">(</span><span class="nl">fontSize:</span> <span class="mi">16</span><span class="p">)),</span>
    <span class="n">SizedBox</span><span class="p">(</span><span class="nl">height:</span> <span class="mi">8</span><span class="p">),</span>
    <span class="n">Text</span><span class="p">(</span><span class="s">'Hobi: Ngoding &amp; Kopi'</span><span class="p">,</span> <span class="nl">style:</span> <span class="n">TextStyle</span><span class="p">(</span><span class="nl">fontSize:</span> <span class="mi">16</span><span class="p">)),</span>
  <span class="p">],</span>
<span class="p">)</span>
</code></pre></div></div>

<h3 id="mainaxisalignment-vs-crossaxisalignment">MainAxisAlignment vs CrossAxisAlignment</h3>

<p>Ini konsep penting banget. Gampangnya:</p>

<ul>
  <li><strong>Main axis</strong> = arah susunan utama. Untuk <code class="language-plaintext highlighter-rouge">Column</code> = vertikal, untuk <code class="language-plaintext highlighter-rouge">Row</code> = horizontal.</li>
  <li><strong>Cross axis</strong> = arah yang tegak lurus. Untuk <code class="language-plaintext highlighter-rouge">Column</code> = horizontal, untuk <code class="language-plaintext highlighter-rouge">Row</code> = vertikal.</li>
</ul>

<table>
  <thead>
    <tr>
      <th>MainAxisAlignment</th>
      <th>Efek</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">start</code></td>
      <td>Rapat atas (Column) / kiri (Row)</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">end</code></td>
      <td>Rapat bawah (Column) / kanan (Row)</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">center</code></td>
      <td>Tengah</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">spaceBetween</code></td>
      <td>Renggang, space di antara item</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">spaceAround</code></td>
      <td>Space merata, setengah di ujung</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">spaceEvenly</code></td>
      <td>Space benar-benar merata</td>
    </tr>
  </tbody>
</table>

<h3 id="contoh-praktek-card-profile">Contoh Praktek: Card Profile</h3>

<p>Gabungin semuanya jadi satu widget keren:</p>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Card</span><span class="p">(</span>
  <span class="nl">elevation:</span> <span class="mi">4</span><span class="p">,</span>
  <span class="nl">shape:</span> <span class="n">RoundedRectangleBorder</span><span class="p">(</span><span class="nl">borderRadius:</span> <span class="n">BorderRadius</span><span class="o">.</span><span class="na">circular</span><span class="p">(</span><span class="mi">16</span><span class="p">)),</span>
  <span class="nl">child:</span> <span class="n">Padding</span><span class="p">(</span>
    <span class="nl">padding:</span> <span class="n">EdgeInsets</span><span class="o">.</span><span class="na">all</span><span class="p">(</span><span class="mi">20</span><span class="p">),</span>
    <span class="nl">child:</span> <span class="n">Row</span><span class="p">(</span>
      <span class="nl">crossAxisAlignment:</span> <span class="n">CrossAxisAlignment</span><span class="o">.</span><span class="na">start</span><span class="p">,</span>
      <span class="nl">children:</span> <span class="p">[</span>
        <span class="c1">// Avatar</span>
        <span class="n">Container</span><span class="p">(</span>
          <span class="nl">width:</span> <span class="mi">60</span><span class="p">,</span>
          <span class="nl">height:</span> <span class="mi">60</span><span class="p">,</span>
          <span class="nl">decoration:</span> <span class="n">BoxDecoration</span><span class="p">(</span>
            <span class="nl">color:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">deepPurple</span><span class="p">,</span>
            <span class="nl">borderRadius:</span> <span class="n">BorderRadius</span><span class="o">.</span><span class="na">circular</span><span class="p">(</span><span class="mi">30</span><span class="p">),</span>
          <span class="p">),</span>
          <span class="nl">child:</span> <span class="n">Icon</span><span class="p">(</span><span class="n">Icons</span><span class="o">.</span><span class="na">person</span><span class="p">,</span> <span class="nl">color:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">white</span><span class="p">,</span> <span class="nl">size:</span> <span class="mi">32</span><span class="p">),</span>
        <span class="p">),</span>
        <span class="n">SizedBox</span><span class="p">(</span><span class="nl">width:</span> <span class="mi">16</span><span class="p">),</span>
        <span class="c1">// Info</span>
        <span class="n">Column</span><span class="p">(</span>
          <span class="nl">crossAxisAlignment:</span> <span class="n">CrossAxisAlignment</span><span class="o">.</span><span class="na">start</span><span class="p">,</span>
          <span class="nl">children:</span> <span class="p">[</span>
            <span class="n">Text</span><span class="p">(</span>
              <span class="s">'Ahsai Dev'</span><span class="p">,</span>
              <span class="nl">style:</span> <span class="n">TextStyle</span><span class="p">(</span>
                <span class="nl">fontSize:</span> <span class="mi">20</span><span class="p">,</span>
                <span class="nl">fontWeight:</span> <span class="n">FontWeight</span><span class="o">.</span><span class="na">bold</span><span class="p">,</span>
              <span class="p">),</span>
            <span class="p">),</span>
            <span class="n">SizedBox</span><span class="p">(</span><span class="nl">height:</span> <span class="mi">4</span><span class="p">),</span>
            <span class="n">Text</span><span class="p">(</span><span class="s">'Flutter Developer • 3 tahun'</span><span class="p">),</span>
            <span class="n">SizedBox</span><span class="p">(</span><span class="nl">height:</span> <span class="mi">8</span><span class="p">),</span>
            <span class="n">Row</span><span class="p">(</span>
              <span class="nl">children:</span> <span class="p">[</span>
                <span class="n">Icon</span><span class="p">(</span><span class="n">Icons</span><span class="o">.</span><span class="na">location_on</span><span class="p">,</span> <span class="nl">size:</span> <span class="mi">16</span><span class="p">,</span> <span class="nl">color:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">grey</span><span class="p">),</span>
                <span class="n">SizedBox</span><span class="p">(</span><span class="nl">width:</span> <span class="mi">4</span><span class="p">),</span>
                <span class="n">Text</span><span class="p">(</span><span class="s">'Jakarta, Indonesia'</span><span class="p">),</span>
              <span class="p">],</span>
            <span class="p">),</span>
          <span class="p">],</span>
        <span class="p">),</span>
      <span class="p">],</span>
    <span class="p">),</span>
  <span class="p">),</span>
<span class="p">)</span>
</code></pre></div></div>

<p>Outputnya: sebuah card profile dengan avatar bulat di kiri, teks dan icon di kanan — rapi, clean, dan cuma pakai <code class="language-plaintext highlighter-rouge">Row</code>, <code class="language-plaintext highlighter-rouge">Column</code>, <code class="language-plaintext highlighter-rouge">Container</code>, dan <code class="language-plaintext highlighter-rouge">Text</code>.</p>

<hr />

<h2 id="tips-anti-pusing-layouting">Tips Anti-Pusing Layouting</h2>

<ol>
  <li><strong>Mulai dari luar ke dalam</strong> — bungkus pakai <code class="language-plaintext highlighter-rouge">Container</code> dulu, baru isi child.</li>
  <li><strong>Row/Column bisa nested</strong> — jangan takut bikin <code class="language-plaintext highlighter-rouge">Column</code> di dalam <code class="language-plaintext highlighter-rouge">Row</code> atau sebaliknya.</li>
  <li><strong><code class="language-plaintext highlighter-rouge">SizedBox</code> is your best friend</strong> — buat kasih jarak antar widget tanpa ribet ngatur margin.</li>
  <li><strong>Gunakan <code class="language-plaintext highlighter-rouge">debugPaintSizeEnabled = true</code></strong> — aktifkan di <code class="language-plaintext highlighter-rouge">main()</code> buat liat garis batas setiap widget. Super membantu debugging layout!</li>
</ol>

<hr />

<h2 id="kesimpulan">Kesimpulan</h2>

<p>Empat widget ini adalah fondasi dari semua UI Flutter:</p>

<table>
  <thead>
    <tr>
      <th>Widget</th>
      <th>Fungsi</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">Text</code></td>
      <td>Menampilkan tulisan</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">Container</code></td>
      <td>Kotak pembungkus dengan styling</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">Row</code></td>
      <td>Susun child horizontal</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">Column</code></td>
      <td>Susun child vertikal</td>
    </tr>
  </tbody>
</table>

<p>Kuasai empat ini dulu, nanti widget lain seperti <code class="language-plaintext highlighter-rouge">ListView</code>, <code class="language-plaintext highlighter-rouge">Stack</code>, <code class="language-plaintext highlighter-rouge">GridView</code> bakal jauh lebih gampang dipahami. Di artikel selanjutnya, kita masuk ke <strong>State</strong>: bedanya <code class="language-plaintext highlighter-rouge">StatelessWidget</code> dan <code class="language-plaintext highlighter-rouge">StatefulWidget</code> serta magic-nya <code class="language-plaintext highlighter-rouge">setState</code>.</p>

<hr />

<p>Sampai jumpa di artikel #5! Kalau ada pertanyaan, pantau terus blog ini — tiap hari ada tutorial baru. Jangan lupa share ke sosial media dan tag <a href="https://ahsai.my.id">@ahsai001</a>. Selamat ngoding! 🚀</p>]]></content><author><name></name></author><category term="flutter" /><category term="dart" /><category term="tutorial" /><category term="indonesia" /><category term="pemrograman" /><category term="widget" /><summary type="html"><![CDATA[Halo, teman-teman! Setelah kita kenalan sama Flutter (#1), install SDK-nya (#2), dan paham struktur project (#3), sekarang saatnya masuk ke bagian paling seru: ngoding UI! Di Flutter, semuanya adalah widget. Mau bikin teks? Widget. Mau bikin kotak? Widget. Mau atur tata letak? Widget lagi. Jadi hari ini kita bahas empat widget paling fundamental yang bakal kamu pakai di setiap halaman: Text, Container, Row, dan Column.]]></summary></entry><entry><title type="html">Flutter #3: Struktur Project Flutter &amp;amp; File Penting yang Wajib Kamu Kenali</title><link href="https://ahsai.my.id/blog/2026/08/05/flutter-3-struktur-project-flutter-file-penting/" rel="alternate" type="text/html" title="Flutter #3: Struktur Project Flutter &amp;amp; File Penting yang Wajib Kamu Kenali" /><published>2026-08-05T00:00:00+00:00</published><updated>2026-08-05T00:00:00+00:00</updated><id>https://ahsai.my.id/blog/2026/08/05/flutter-3-struktur-project-flutter-file-penting</id><content type="html" xml:base="https://ahsai.my.id/blog/2026/08/05/flutter-3-struktur-project-flutter-file-penting/"><![CDATA[<h2 id="pengantar">Pengantar</h2>

<p>Di artikel sebelumnya kita sudah berhasil install Flutter SDK dan setup IDE. Waktu kamu jalankan <code class="language-plaintext highlighter-rouge">flutter create</code> pertama kali, Flutter langsung menghasilkan puluhan file dan folder. Jangan panik! Hari ini kita akan membedah satu per satu — mana yang penting, mana yang bisa kamu abaikan, dan apa fungsi masing-masing.</p>

<hr />

<h2 id="membuat-project-baru">Membuat Project Baru</h2>

<p>Buka terminal dan jalankan:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>flutter create belajar_flutter
<span class="nb">cd </span>belajar_flutter
</code></pre></div></div>

<p>Setelah perintah selesai, buka folder <code class="language-plaintext highlighter-rouge">belajar_flutter</code> di VS Code atau Android Studio. Kamu akan melihat struktur seperti ini:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>belajar_flutter/
├── .dart_tool/
├── .idea/
├── android/
├── ios/
├── lib/
│   └── main.dart
├── linux/
├── macos/
├── test/
│   └── widget_test.dart
├── web/
├── windows/
├── .gitignore
├── .metadata
├── analysis_options.yaml
├── pubspec.yaml
└── README.md
</code></pre></div></div>

<hr />

<h2 id="folder--file-penting-wajib-tahu">Folder &amp; File Penting (Wajib Tahu)</h2>

<h3 id="1-libmaindart--jantung-aplikasi">1. <code class="language-plaintext highlighter-rouge">lib/main.dart</code> — <strong>Jantung Aplikasi</strong></h3>

<p>Ini adalah <strong>entry point</strong> aplikasi Flutter-mu. Setiap kali Flutter dijalankan, ia akan mencari fungsi <code class="language-plaintext highlighter-rouge">main()</code> di file ini. Buka <code class="language-plaintext highlighter-rouge">main.dart</code>, kamu akan melihat kode default:</p>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="s">'package:flutter/material.dart'</span><span class="o">;</span>

<span class="kt">void</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
  <span class="n">runApp</span><span class="p">(</span><span class="kd">const</span> <span class="n">MyApp</span><span class="p">());</span>
<span class="p">}</span>

<span class="kd">class</span> <span class="nc">MyApp</span> <span class="kd">extends</span> <span class="n">StatelessWidget</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="n">MyApp</span><span class="p">({</span><span class="k">super</span><span class="o">.</span><span class="na">key</span><span class="p">});</span>

  <span class="nd">@override</span>
  <span class="n">Widget</span> <span class="n">build</span><span class="p">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span> <span class="n">MaterialApp</span><span class="p">(</span>
      <span class="nl">title:</span> <span class="s">'Flutter Demo'</span><span class="p">,</span>
      <span class="nl">theme:</span> <span class="n">ThemeData</span><span class="p">(</span>
        <span class="nl">colorScheme:</span> <span class="n">ColorScheme</span><span class="o">.</span><span class="na">fromSeed</span><span class="p">(</span><span class="nl">seedColor:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">deepPurple</span><span class="p">),</span>
        <span class="nl">useMaterial3:</span> <span class="kc">true</span><span class="p">,</span>
      <span class="p">),</span>
      <span class="nl">home:</span> <span class="kd">const</span> <span class="n">MyHomePage</span><span class="p">(</span><span class="nl">title:</span> <span class="s">'Flutter Demo Home Page'</span><span class="p">),</span>
    <span class="p">);</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<blockquote>
  <p><strong>Ingat:</strong> <code class="language-plaintext highlighter-rouge">main()</code> adalah fungsi pertama yang dipanggil. Tanpa <code class="language-plaintext highlighter-rouge">main()</code>, aplikasi gak akan jalan.</p>
</blockquote>

<h3 id="2-pubspecyaml--manager-dependencies--aset">2. <code class="language-plaintext highlighter-rouge">pubspec.yaml</code> — <strong>Manager Dependencies &amp; Aset</strong></h3>

<p>File YAML ini adalah pusat konfigurasi project. Di sini kamu mendaftarkan:</p>
<ul>
  <li><strong>Dependencies (package)</strong>: library dari pub.dev</li>
  <li><strong>Aset (gambar, font, JSON)</strong>: file statis yang dipakai aplikasi</li>
  <li><strong>Metadata project</strong>: nama, deskripsi, versi</li>
</ul>

<p>Contoh <code class="language-plaintext highlighter-rouge">pubspec.yaml</code>:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">name</span><span class="pi">:</span> <span class="s">belajar_flutter</span>
<span class="na">description</span><span class="pi">:</span> <span class="s">Aplikasi Flutter pertamaku</span>
<span class="na">version</span><span class="pi">:</span> <span class="s">1.0.0+1</span>

<span class="na">environment</span><span class="pi">:</span>
  <span class="na">sdk</span><span class="pi">:</span> <span class="s2">"</span><span class="s">&gt;=3.0.0</span><span class="nv"> </span><span class="s">&lt;4.0.0"</span>

<span class="na">dependencies</span><span class="pi">:</span>
  <span class="na">flutter</span><span class="pi">:</span>
    <span class="na">sdk</span><span class="pi">:</span> <span class="s">flutter</span>
  <span class="c1"># Tambahkan package dari pub.dev di sini</span>
  <span class="na">http</span><span class="pi">:</span> <span class="s">^1.1.0</span>

<span class="na">dev_dependencies</span><span class="pi">:</span>
  <span class="na">flutter_test</span><span class="pi">:</span>
    <span class="na">sdk</span><span class="pi">:</span> <span class="s">flutter</span>
  <span class="na">flutter_lints</span><span class="pi">:</span> <span class="s">^3.0.0</span>

<span class="na">flutter</span><span class="pi">:</span>
  <span class="c1"># Daftarkan aset di sini</span>
  <span class="na">assets</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="s">assets/images/</span>
    <span class="pi">-</span> <span class="s">assets/data/sample.json</span>
  <span class="c1"># Font custom</span>
  <span class="na">fonts</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">family</span><span class="pi">:</span> <span class="s">Poppins</span>
      <span class="na">fonts</span><span class="pi">:</span>
        <span class="pi">-</span> <span class="na">asset</span><span class="pi">:</span> <span class="s">assets/fonts/Poppins-Regular.ttf</span>
</code></pre></div></div>

<blockquote>
  <p><strong>Tips:</strong> Setiap kali kamu mengubah <code class="language-plaintext highlighter-rouge">pubspec.yaml</code>, jalankan <code class="language-plaintext highlighter-rouge">flutter pub get</code> di terminal untuk mengunduh dependency baru.</p>
</blockquote>

<h3 id="3-android--ios--kode-native-platform">3. <code class="language-plaintext highlighter-rouge">android/</code> &amp; <code class="language-plaintext highlighter-rouge">ios/</code> — <strong>Kode Native Platform</strong></h3>

<p>Folder ini berisi project Android dan iOS native. Kamu <strong>gak perlu sering-sering menyentuhnya</strong> kecuali:</p>
<ul>
  <li>Konfigurasi permission (kamera, lokasi, notifikasi)</li>
  <li>Setup Firebase (menambahkan <code class="language-plaintext highlighter-rouge">google-services.json</code> / <code class="language-plaintext highlighter-rouge">GoogleService-Info.plist</code>)</li>
  <li>Menambahkan kemampuan platform-specific (misalnya widget native)</li>
</ul>

<h3 id="4-testwidget_testdart--testing">4. <code class="language-plaintext highlighter-rouge">test/widget_test.dart</code> — <strong>Testing</strong></h3>

<p>Flutter otomatis menyediakan satu file test sederhana. Ini folder tempat kamu menulis <strong>unit test</strong> dan <strong>widget test</strong>. Sangat penting saat aplikasi mulai besar — kita bahas lebih detail di artikel lanjutan.</p>

<h3 id="5-analysis_optionsyaml--aturan-kode">5. <code class="language-plaintext highlighter-rouge">analysis_options.yaml</code> — <strong>Aturan Kode</strong></h3>

<p>File ini mengatur static analysis — semacam “linter” yang membantu kamu menulis kode Dart yang rapi dan konsisten:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">include</span><span class="pi">:</span> <span class="s">package:flutter_lints/flutter.yaml</span>

<span class="na">linter</span><span class="pi">:</span>
  <span class="na">rules</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="s">prefer_const_constructors</span>
    <span class="pi">-</span> <span class="s">prefer_const_literals_to_create_immutables</span>
    <span class="pi">-</span> <span class="s">avoid_print</span>
</code></pre></div></div>

<hr />

<h2 id="folder-yang-bisa-diabaikan-untuk-sekarang">Folder yang Bisa Diabaikan (Untuk Sekarang)</h2>

<table>
  <thead>
    <tr>
      <th>Folder</th>
      <th>Fungsi</th>
      <th>Jangan Dihapus?</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">.dart_tool/</code></td>
      <td>Cache internal Dart</td>
      <td>Jangan dihapus manual, gitignore sudah menanganinya</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">.idea/</code></td>
      <td>Konfigurasi IntelliJ/Android Studio</td>
      <td>Bisa dihapus kalau pakai VS Code</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">linux/</code>, <code class="language-plaintext highlighter-rouge">macos/</code>, <code class="language-plaintext highlighter-rouge">windows/</code></td>
      <td>Platform desktop</td>
      <td>Biarkan saja, Flutter tangani otomatis</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">web/</code></td>
      <td>Konfigurasi Flutter Web</td>
      <td>Nanti berguna kalau deploy ke web</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">.metadata</code></td>
      <td>Versi Flutter yang dipakai</td>
      <td>Jangan diubah manual</td>
    </tr>
  </tbody>
</table>

<hr />

<h2 id="gitignore--file-wajib-saat-push-ke-git"><code class="language-plaintext highlighter-rouge">.gitignore</code> — File Wajib Saat Push ke Git</h2>

<p>File ini sudah disediakan Flutter secara otomatis. Isinya mendaftarkan file/folder yang <strong>tidak</strong> akan di-push ke GitHub:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># Contoh isi .gitignore Flutter
.dart_tool/
.packages
build/
*.iml
.idea/
.metadata
</code></pre></div></div>

<p>Dengan <code class="language-plaintext highlighter-rouge">.gitignore</code>, project kamu tetap bersih dan tidak membawa file sampah saat di-commit.</p>

<hr />

<h2 id="tips-praktis">Tips Praktis</h2>

<ol>
  <li><strong>Jangan hapus <code class="language-plaintext highlighter-rouge">pubspec.lock</code></strong> — file ini mencatat versi spesifik dari setiap dependency agar project tetap konsisten antar developer.</li>
  <li><strong>Gunakan folder <code class="language-plaintext highlighter-rouge">lib/</code> untuk semua kode Dart</strong> — Flutter mencari kode di sini. Jangan taruh kode aplikasi di root folder.</li>
  <li><strong>Strukturkan <code class="language-plaintext highlighter-rouge">lib/</code> sejak awal</strong> — walaupun masih kecil, biasakan bagi kode ke subfolder seperti <code class="language-plaintext highlighter-rouge">screens/</code>, <code class="language-plaintext highlighter-rouge">widgets/</code>, <code class="language-plaintext highlighter-rouge">models/</code>, <code class="language-plaintext highlighter-rouge">services/</code>. Nanti kita bahas arsitektur di artikel lanjutan.</li>
  <li><strong>Selalu jalankan <code class="language-plaintext highlighter-rouge">flutter doctor</code></strong> setelah install package baru atau mengubah konfigurasi platform — untuk memastikan semuanya OK.</li>
</ol>

<hr />

<h2 id="penutup">Penutup</h2>

<p>Sekarang kamu sudah paham isi setiap folder dan file di project Flutter. Dengan pemahaman ini, kamu gak akan bingung lagi saat membaca tutorial atau debugging error yang menyangkut path file.</p>

<p><strong>Selanjutnya:</strong> Kita akan mulai ngoding beneran — mengenal <strong>Widget Dasar: Text, Container, Row, dan Column</strong> sebagai fondasi UI Flutter!</p>

<hr />

<p>Sampai jumpa di artikel berikutnya. Coba sendiri struktur project yang udah kita bahas, lalu share ke sosial media dan tag <strong>@ahsai001</strong> — aku penasaran lihat progress belajarmu! 🚀</p>]]></content><author><name></name></author><category term="flutter" /><category term="dart" /><category term="mobile" /><category term="tutorial" /><summary type="html"><![CDATA[Pengantar]]></summary></entry><entry><title type="html">Flutter #1: Apa itu Flutter &amp;amp; Kenapa Harus Flutter</title><link href="https://ahsai.my.id/blog/2026/08/04/flutter-1-apa-itu-flutter/" rel="alternate" type="text/html" title="Flutter #1: Apa itu Flutter &amp;amp; Kenapa Harus Flutter" /><published>2026-08-04T00:00:00+00:00</published><updated>2026-08-04T00:00:00+00:00</updated><id>https://ahsai.my.id/blog/2026/08/04/flutter-1-apa-itu-flutter</id><content type="html" xml:base="https://ahsai.my.id/blog/2026/08/04/flutter-1-apa-itu-flutter/"><![CDATA[<h2 id="pengantar">Pengantar</h2>

<p>Selamat datang di seri <strong>“Flutter dari Nol sampai Expert”</strong>! Ini adalah artikel pertama dari 90 artikel yang akan menemanimu dari benar-benar nol — bahkan belum pernah install Flutter — sampai bisa bikin aplikasi full-stack dan deploy ke production. Kenapa kita mulai dari “apa itu”? Karena sebelum nyemplung, penting buat paham <em>big picture</em>-nya dulu.</p>

<hr />

<h2 id="apa-itu-flutter">Apa Itu Flutter?</h2>

<p>Flutter adalah <strong>UI toolkit open-source</strong> buatan Google untuk membangun aplikasi yang dikompilasi secara native. Satu kode, banyak platform: <strong>Android, iOS, Web, Windows, macOS, dan Linux</strong>. Flutter pertama kali dirilis tahun 2017 (versi alpha) dan sekarang sudah versi 3.x yang sangat matang.</p>

<p>Bahasa pemrograman yang dipakai: <strong>Dart</strong> — juga buatan Google, didesain khusus untuk UI development.</p>

<p>Coba lihat perbandingan sederhana ini:</p>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// React Native (JavaScript)</span>
<span class="p">&lt;</span><span class="n">View</span> <span class="n">style</span><span class="o">=</span><span class="p">&gt;</span>
  <span class="p">&lt;</span><span class="n">Text</span><span class="p">&gt;</span><span class="n">Halo</span><span class="p">&lt;</span><span class="o">/</span><span class="n">Text</span><span class="p">&gt;</span>
  <span class="p">&lt;</span><span class="n">Text</span><span class="p">&gt;</span><span class="n">Dunia</span><span class="p">&lt;</span><span class="o">/</span><span class="n">Text</span><span class="p">&gt;</span>
<span class="p">&lt;</span><span class="o">/</span><span class="n">View</span><span class="p">&gt;</span>

<span class="c1">// Flutter (Dart)</span>
<span class="n">Row</span><span class="p">(</span>
  <span class="nl">children:</span> <span class="p">[</span>
    <span class="n">Text</span><span class="p">(</span><span class="s">'Halo'</span><span class="p">),</span>
    <span class="n">Text</span><span class="p">(</span><span class="s">'Dunia'</span><span class="p">),</span>
  <span class="p">],</span>
<span class="p">)</span>
</code></pre></div></div>

<p>Semua di Flutter adalah <strong>widget</strong> — dari <code class="language-plaintext highlighter-rouge">Text</code>, <code class="language-plaintext highlighter-rouge">Container</code>, <code class="language-plaintext highlighter-rouge">Row</code>, sampai <code class="language-plaintext highlighter-rouge">Scaffold</code>. Gak ada markup terpisah seperti XML atau HTML. Semuanya ditulis dalam Dart. Ini bikin kode lebih konsisten dan gampang dibaca.</p>

<hr />

<h2 id="kenapa-harus-flutter">Kenapa Harus Flutter?</h2>

<h3 id="1-hot-reload--fitur-paling-bikin-candu">1. <strong>Hot Reload — Fitur Paling Bikin Candu</strong></h3>

<p>Hot Reload memungkinkan kamu lihat perubahan kode dalam <strong>kurang dari 1 detik</strong> tanpa harus rebuild ulang aplikasi. Ubah warna tombol? Simpan. Langsung muncul. Ini <strong>super produktif</strong> untuk iterasi desain UI.</p>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Ubah warna dari biru ke ungu, Ctrl+S, boom — langsung berubah!</span>
<span class="n">ElevatedButton</span><span class="p">(</span>
  <span class="nl">style:</span> <span class="n">ElevatedButton</span><span class="o">.</span><span class="na">styleFrom</span><span class="p">(</span>
    <span class="nl">backgroundColor:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">deepPurple</span><span class="p">,</span> <span class="c1">// ganti ini, hot reload!</span>
  <span class="p">),</span>
  <span class="nl">onPressed:</span> <span class="p">()</span> <span class="p">{},</span>
  <span class="nl">child:</span> <span class="n">Text</span><span class="p">(</span><span class="s">'Klik Aku'</span><span class="p">),</span>
<span class="p">),</span>
</code></pre></div></div>

<h3 id="2-single-codebase-multi-platform">2. <strong>Single Codebase, Multi-Platform</strong></h3>

<p>Satu project Flutter bisa kamu build jadi:</p>
<ul>
  <li>APK/AAB untuk Android</li>
  <li>IPA untuk iOS</li>
  <li>Web app (HTML/JS/CSS)</li>
  <li>Desktop app (Windows, macOS, Linux)</li>
</ul>

<p>Bukan cuma “jalan”, tapi <strong>benar-benar native performance</strong>. Flutter gak pakai bridge seperti React Native; dia render langsung ke canvas menggunakan engine Skia.</p>

<h3 id="3-widget-system-yang-konsisten">3. <strong>Widget System yang Konsisten</strong></h3>

<p>Material Design (Android-style) dan Cupertino (iOS-style) tersedia out-of-the-box. Mau tampilan yang persis native di kedua platform? Flutter bisa.</p>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Android-style switch</span>
<span class="n">Switch</span><span class="p">(</span><span class="nl">value:</span> <span class="kc">true</span><span class="p">,</span> <span class="nl">onChanged:</span> <span class="p">(</span><span class="n">v</span><span class="p">)</span> <span class="p">{})</span>

<span class="c1">// iOS-style switch</span>
<span class="n">CupertinoSwitch</span><span class="p">(</span><span class="nl">value:</span> <span class="kc">true</span><span class="p">,</span> <span class="nl">onChanged:</span> <span class="p">(</span><span class="n">v</span><span class="p">)</span> <span class="p">{})</span>
</code></pre></div></div>

<h3 id="4-ekosistem--komunitas-besar">4. <strong>Ekosistem &amp; Komunitas Besar</strong></h3>

<ul>
  <li><strong>pub.dev</strong> punya 40.000+ package siap pakai</li>
  <li>Dokumentasi resmi Google sangat lengkap</li>
  <li>Komunitas global aktif — StackOverflow, Discord, Reddit, GitHub</li>
  <li>500.000+ apps di Google Play dibuat dengan Flutter</li>
</ul>

<h3 id="5-performa-native">5. <strong>Performa Native</strong></h3>

<p>Flutter compile ke ARM/x86 native code (bukan interpreted). Tidak ada JavaScript bridge. Jadi performanya mendekati aplikasi native murni — 60 FPS smooth, bahkan untuk animasi kompleks.</p>

<hr />

<h2 id="arsitektur-flutter-gambaran-singkat">Arsitektur Flutter (Gambaran Singkat)</h2>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>┌─────────────────────────┐
│     Framework (Dart)     │  ← Kamu coding di sini
│  Material / Cupertino    │
│  Widgets / Rendering     │
├─────────────────────────┤
│     Engine (C++)         │  ← Skia, Dart VM, Text
├─────────────────────────┤
│    Embedder (Platform)   │  ← Android/iOS/Web/Desktop
└─────────────────────────┘
</code></pre></div></div>

<blockquote>
  <p><strong>Gak perlu hafal sekarang</strong> — nanti di artikel-artikel selanjutnya kamu bakal paham layer demi layer.</p>
</blockquote>

<hr />

<h2 id="mitos--fakta-flutter">Mitos &amp; Fakta Flutter</h2>

<table>
  <thead>
    <tr>
      <th>Mitos</th>
      <th>Fakta</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>“Flutter cuma buat Android”</td>
      <td>Flutter support <strong>6 platform</strong></td>
    </tr>
    <tr>
      <td>“Flutter lambat”</td>
      <td>Flutter compile ke native code, 60 FPS</td>
    </tr>
    <tr>
      <td>“Dart susah dipelajari”</td>
      <td>Mirip Java/JavaScript/C#, 1-2 minggu udah nyaman</td>
    </tr>
    <tr>
      <td>“Flutter gak cocok buat app besar”</td>
      <td>Google Ads, Alibaba, BMW, Grab pakai Flutter</td>
    </tr>
  </tbody>
</table>

<hr />

<h2 id="siapa-yang-pakai-flutter-di-dunia-nyata">Siapa yang Pakai Flutter di Dunia Nyata?</h2>

<ul>
  <li><strong>Google Ads</strong> — dashboard iklan</li>
  <li><strong>Alibaba</strong> (Xianyu app) — 50+ juta pengguna</li>
  <li><strong>BMW</strong> — My BMW App</li>
  <li><strong>Grab</strong> — GrabMerchant, GrabFood</li>
  <li><strong>Tokopedia, Gojek</strong> — beberapa micro-app internal</li>
  <li><strong>Reflectly</strong> — journal app pemenang design award</li>
</ul>

<hr />

<h2 id="latihan-hari-ini">Latihan Hari Ini</h2>

<p>Karena ini masih artikel perkenalan, latihanmu ringan aja:</p>

<ol>
  <li><strong>Google</strong>: Cari 3 aplikasi di Play Store yang dibuat pakai Flutter selain yang disebutkan di atas</li>
  <li><strong>Refleksi</strong>: Tulis di notes kenapa <em>kamu</em> mau belajar Flutter — apa yang pengen kamu bikin?</li>
  <li><strong>Install</strong>: Kalau udah gak sabaran, install Flutter SDK (step-by-step di artikel besok!)</li>
</ol>

<hr />

<h2 id="apa-selanjutnya">Apa Selanjutnya?</h2>

<p>Besok kita bakal <strong>install Flutter SDK &amp; setup IDE</strong> — dari nol sampai project pertama berhasil jalan di emulator. Pastikan laptopmu siap: RAM minimal 8GB, storage kosong ~20GB.</p>

<blockquote>
  <p>🚀 Coba sendiri, share hasilnya ke sosial media! Tag @ahsai001 — siapa tahu ada yang mau belajar bareng.</p>
</blockquote>

<hr />

<p><em>Seri “Flutter dari Nol sampai Expert” — artikel ke-1</em></p>]]></content><author><name></name></author><category term="flutter" /><category term="dart" /><category term="mobile" /><category term="tutorial" /><summary type="html"><![CDATA[Pengantar]]></summary></entry><entry><title type="html">Flutter #2: Install Flutter SDK &amp;amp; Setup IDE (VS Code / Android Studio)</title><link href="https://ahsai.my.id/blog/2026/08/04/flutter-2-install-flutter-sdk-setup-ide/" rel="alternate" type="text/html" title="Flutter #2: Install Flutter SDK &amp;amp; Setup IDE (VS Code / Android Studio)" /><published>2026-08-04T00:00:00+00:00</published><updated>2026-08-04T00:00:00+00:00</updated><id>https://ahsai.my.id/blog/2026/08/04/flutter-2-install-flutter-sdk-setup-ide</id><content type="html" xml:base="https://ahsai.my.id/blog/2026/08/04/flutter-2-install-flutter-sdk-setup-ide/"><![CDATA[<h2 id="pengantar">Pengantar</h2>

<p>Di artikel pertama kita sudah kenalan dengan Flutter dan alasan kenapa framework ini layak dipelajari. Sekarang saatnya aksi: <strong>install Flutter SDK, setup IDE, dan jalankan project pertama!</strong> Tenang, step-by-step-nya bakal gampang banget — bahkan kalau kamu belum pernah setup environment development sebelumnya.</p>

<hr />

<h2 id="langkah-1-install-flutter-sdk">Langkah 1: Install Flutter SDK</h2>

<h3 id="windows">Windows</h3>

<ol>
  <li>Download Flutter SDK dari <a href="https://docs.flutter.dev/get-started/install/windows">flutter.dev</a></li>
  <li>Ekstrak ZIP ke <code class="language-plaintext highlighter-rouge">C:\src\flutter</code> (hindari folder seperti <code class="language-plaintext highlighter-rouge">C:\Program Files</code> yang butuh permission khusus)</li>
  <li>Tambahkan Flutter ke <strong>Environment Variables → Path</strong>: <code class="language-plaintext highlighter-rouge">C:\src\flutter\bin</code></li>
  <li>Buka Command Prompt atau PowerShell, jalankan:</li>
</ol>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>flutter doctor
</code></pre></div></div>

<h3 id="macos">macOS</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Pakai Homebrew (paling gampang)</span>
brew <span class="nb">install</span> <span class="nt">--cask</span> flutter

<span class="c"># Atau download manual dari flutter.dev</span>
</code></pre></div></div>

<h3 id="linux">Linux</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Download &amp; ekstrak ke ~/development/</span>
<span class="nb">cd</span> ~/development
wget https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_latest.tar.xz
<span class="nb">tar </span>xf flutter_linux_latest.tar.xz

<span class="c"># Tambahkan ke PATH (masukkan ke ~/.bashrc atau ~/.zshrc)</span>
<span class="nb">export </span><span class="nv">PATH</span><span class="o">=</span><span class="s2">"</span><span class="nv">$PATH</span><span class="s2">:</span><span class="nv">$HOME</span><span class="s2">/development/flutter/bin"</span>

<span class="c"># Reload</span>
<span class="nb">source</span> ~/.bashrc
</code></pre></div></div>

<blockquote>
  <p><strong>PENTING</strong>: Setelah install, selalu jalankan <code class="language-plaintext highlighter-rouge">flutter doctor</code> — command ini akan kasih tahu apa saja yang masih kurang.</p>
</blockquote>

<hr />

<h2 id="langkah-2-pahami-output-flutter-doctor">Langkah 2: Pahami Output <code class="language-plaintext highlighter-rouge">flutter doctor</code></h2>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>flutter doctor
Doctor summary <span class="o">(</span>to see all details, run flutter doctor <span class="nt">-v</span><span class="o">)</span>:
<span class="o">[</span>✓] Flutter <span class="o">(</span>Channel stable, 3.x<span class="o">)</span>
<span class="o">[</span>✓] Android toolchain - develop <span class="k">for </span>Android devices
<span class="o">[</span>✓] Chrome - develop <span class="k">for </span>the web
<span class="o">[</span>✓] Android Studio
<span class="o">[</span>✓] VS Code
<span class="o">[</span>✓] Connected device
</code></pre></div></div>

<p>Setiap tanda centang hijau artinya siap. Kalau ada tanda silang <code class="language-plaintext highlighter-rouge">[✗]</code> atau tanda seru <code class="language-plaintext highlighter-rouge">[!]</code>, Flutter akan kasih petunjuk apa yang perlu kamu install/fix. <strong>Jangan skip ini!</strong> Banyak error di kemudian hari gara-gara <code class="language-plaintext highlighter-rouge">flutter doctor</code> gak bersih.</p>

<h3 id="hal-yang-biasanya-perlu-diinstall">Hal yang biasanya perlu diinstall:</h3>

<table>
  <thead>
    <tr>
      <th>Item</th>
      <th>Solusi</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">[✗] Android toolchain</code></td>
      <td>Install Android Studio, lalu buka SDK Manager</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">[✗] Android license</code></td>
      <td>Jalankan <code class="language-plaintext highlighter-rouge">flutter doctor --android-licenses</code></td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">[!] Chrome</code></td>
      <td>Install Google Chrome (untuk web development)</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">[✗] Visual Studio</code></td>
      <td>Hanya untuk Windows desktop development</td>
    </tr>
  </tbody>
</table>

<hr />

<h2 id="langkah-3-setup-ide">Langkah 3: Setup IDE</h2>

<p>Kamu bisa pilih <strong>VS Code</strong> (ringan, rekomendasi untuk pemula) atau <strong>Android Studio</strong> (fitur lengkap, lebih berat).</p>

<h3 id="vs-code-rekomendasi">VS Code (Rekomendasi)</h3>

<ol>
  <li>Install <a href="https://code.visualstudio.com/">VS Code</a></li>
  <li>Buka Extensions (<code class="language-plaintext highlighter-rouge">Ctrl+Shift+X</code>), cari dan install:
    <ul>
      <li><strong>Flutter</strong> (otomatis juga install Dart extension)</li>
    </ul>
  </li>
</ol>

<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Setelah install extension, test dengan:</span>
<span class="c1">// Ctrl+Shift+P → ketik "Flutter: New Project"</span>
</code></pre></div></div>

<h3 id="android-studio">Android Studio</h3>

<ol>
  <li>Install <a href="https://developer.android.com/studio">Android Studio</a></li>
  <li>Saat setup, pastikan centang <strong>Android SDK</strong>, <strong>Android SDK Platform-Tools</strong>, dan <strong>Android Emulator</strong></li>
  <li>Buka <strong>SDK Manager</strong> → tab <strong>SDK Tools</strong> → centang <strong>Android SDK Command-line Tools</strong></li>
  <li>Install plugin Flutter: <strong>File → Settings → Plugins → cari “Flutter”</strong></li>
</ol>

<hr />

<h2 id="langkah-4-setup-emulator--device">Langkah 4: Setup Emulator &amp; Device</h2>

<h3 id="opsi-a-android-emulator-built-in">Opsi A: Android Emulator (Built-in)</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Di terminal, list emulator yang tersedia</span>
flutter emulators

<span class="c"># Jalankan emulator tertentu</span>
flutter emulators <span class="nt">--launch</span> Pixel_7_API_34
</code></pre></div></div>

<p>Atau lewat Android Studio: <strong>AVD Manager → Create Virtual Device → pilih device → pilih system image → Finish → Play</strong>.</p>

<h3 id="opsi-b-chrome-untuk-web">Opsi B: Chrome (untuk Web)</h3>

<p>Paling simpel: <code class="language-plaintext highlighter-rouge">flutter run</code> otomatis detect Chrome dan jalankan versi web.</p>

<h3 id="opsi-c-real-device-android">Opsi C: Real Device (Android)</h3>

<ol>
  <li>Aktifkan <strong>Developer Options</strong> di HP (Settings → About Phone → tap “Build Number” 7x)</li>
  <li>Aktifkan <strong>USB Debugging</strong></li>
  <li>Colok HP ke laptop pakai kabel USB</li>
  <li>Accept “Allow USB debugging” dialog di HP</li>
  <li>Verify: <code class="language-plaintext highlighter-rouge">flutter devices</code></li>
</ol>

<hr />

<h2 id="langkah-5-buat-project-pertama--jalankan">Langkah 5: Buat Project Pertama &amp; Jalankan!</h2>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Buat project baru</span>
flutter create nama_aplikasi_pertamamu

<span class="c"># Masuk ke folder project</span>
<span class="nb">cd </span>nama_aplikasi_pertamamu

<span class="c"># Jalankan!</span>
flutter run
</code></pre></div></div>

<p>Kalau berhasil, kamu akan lihat aplikasi <strong>counter default Flutter</strong> — tombol <code class="language-plaintext highlighter-rouge">+</code> di kanan bawah, tiap tap angka bertambah.</p>

<h3 id="struktur-file-yang-terbentuk">Struktur file yang terbentuk:</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>nama_aplikasi_pertamamu/
├── lib/
│   └── main.dart          ← Tempat kamu ngoding!
├── android/               ← Native Android project
├── ios/                   ← Native iOS project
├── web/                   ← Web config
├── <span class="nb">test</span>/                  ← Unit <span class="nb">test</span>
├── pubspec.yaml           ← Dependencies &amp; asset config
└── README.md
</code></pre></div></div>

<p>Coba buka <code class="language-plaintext highlighter-rouge">lib/main.dart</code> — isinya kode Dart yang menghasilkan tampilan aplikasi counter tadi. Kita akan bahas struktur project lebih detail di artikel berikutnya.</p>

<hr />

<h2 id="tips--troubleshooting">Tips &amp; Troubleshooting</h2>

<h3 id="error-cmdline-tools-component-is-missing">Error: <code class="language-plaintext highlighter-rouge">cmdline-tools component is missing</code></h3>
<p>Jalanin: <strong>Android Studio → SDK Manager → SDK Tools → centang “Android SDK Command-line Tools” → Apply</strong></p>

<h3 id="error-unable-to-locate-android-sdk">Error: <code class="language-plaintext highlighter-rouge">Unable to locate Android SDK</code></h3>
<p>Pastikan <code class="language-plaintext highlighter-rouge">ANDROID_HOME</code> environment variable ter-set ke lokasi SDK (biasanya <code class="language-plaintext highlighter-rouge">C:\Users\NAMA\AppData\Local\Android\Sdk</code> di Windows, <code class="language-plaintext highlighter-rouge">/Users/NAMA/Library/Android/sdk</code> di macOS).</p>

<h3 id="error-emulator-gak-muncul">Error: Emulator gak muncul</h3>
<p>Coba restart ADB: <code class="language-plaintext highlighter-rouge">adb kill-server &amp;&amp; adb start-server</code></p>

<h3 id="flutter-gak-kedetect-di-terminal-windows">Flutter gak kedetect di terminal (Windows)</h3>
<p>Tutup dan buka ulang Command Prompt setelah set PATH. Atau restart PC.</p>

<hr />

<h2 id="latihan-hari-ini">Latihan Hari Ini</h2>

<ol>
  <li><strong>Install Flutter</strong> sampai <code class="language-plaintext highlighter-rouge">flutter doctor</code> minimal 4 centang hijau (Flutter, Chrome, VS Code/Android Studio, Connected device)</li>
  <li><strong>Buat project baru</strong>, ganti teks “You have pushed the button…” di <code class="language-plaintext highlighter-rouge">lib/main.dart</code> jadi kalimat versimu sendiri, lalu jalankan</li>
  <li><strong>Screenshot</strong> hasil <code class="language-plaintext highlighter-rouge">flutter doctor</code> dan aplikasi pertamamu — share ke sosial media, tag @ahsai001!</li>
</ol>

<hr />

<h2 id="apa-selanjutnya">Apa Selanjutnya?</h2>

<p>Besok kita akan bahas <strong>Struktur Project Flutter &amp; File Penting</strong> — kamu akan paham fungsi setiap folder dan file yang digenerate oleh <code class="language-plaintext highlighter-rouge">flutter create</code>, termasuk <code class="language-plaintext highlighter-rouge">pubspec.yaml</code>, <code class="language-plaintext highlighter-rouge">lib/</code>, dan cara mengorganisir kode dengan benar.</p>

<blockquote>
  <p>🚀 Install Flutter sekarang juga! 15 menit aja udah bisa lihat aplikasi jalan. Share pengalaman pertamamu — tag @ahsai001!</p>
</blockquote>

<hr />

<p><em>Seri “Flutter dari Nol sampai Expert” — artikel ke-2</em></p>]]></content><author><name></name></author><category term="flutter" /><category term="dart" /><category term="mobile" /><category term="tutorial" /><summary type="html"><![CDATA[Pengantar]]></summary></entry></feed>