Membuat UI yang menarik dengan Flutter

Flutter adalah toolkit UI Google untuk mem-build aplikasi yang cantik dan dikompilasi secara native dari satu codebase untuk seluler, web, dan desktop. Dalam codelab ini, Anda akan membuat aplikasi chat sederhana untuk Android, iOS, dan (secara opsional) web.

Codelab ini memberikan pembahasan tentang Flutter yang lebih mendalam daripada Menulis Aplikasi Flutter Pertama Anda, bagian 1 dan bagian 2. Jika Anda ingin pengantar Flutter yang lebih sederhana, mulailah dengan codelab tersebut.

Yang Anda pelajari

  • Cara menulis aplikasi Flutter yang terlihat alami di Android dan iOS
  • Cara menggunakan Android Studio IDE, menggunakan banyak pintasan yang didukung oleh plugin Flutter untuk Android Studio dan IntelliJ
  • Cara men-debug aplikasi Flutter Anda
  • Cara menjalankan aplikasi Flutter Anda di emulator, simulator, dan perangkat

Apa yang ingin Anda pelajari dari codelab ini?

Saya baru mengenal topik ini, jadi saya ingin melihat ringkasan yang bagus. Saya sedikit paham soal topik ini, tetapi saya perlu mengingat kembali. Saya sedang mencari kode contoh untuk digunakan dalam project saya. Saya sedang mencari penjelasan tentang sesuatu yang spesifik.

Anda memerlukan dua software untuk menyelesaikan codelab ini: Flutter SDK (download) dan editor (konfigurasikan). Codelab ini mengasumsikan bahwa Anda menggunakan Android Studio, tetapi Anda dapat menggunakan editor pilihan Anda.

Anda dapat menjalankan codelab ini menggunakan perangkat berikut:

  • Perangkat fisik (Android atau iOS) yang terhubung ke komputer Anda dan disetel ke mode developer
  • Emulator Android
  • Simulator iOS
  • Browser Chrome
  • Desktop Windows, macOS, atau Linux (jika Anda mengaktifkan dukungan desktop Flutter)

Jika Anda menjalankannya di Android, Anda harus melakukan penyiapan di Android Studio. Jika Anda menjalankannya di iOS, Anda juga harus memiliki Xcode yang telah diinstal di Mac. Untuk informasi selengkapnya, lihat Set up an editor.

Buat aplikasi Flutter dengan template sederhana. Anda akan memodifikasi aplikasi awal ini untuk membuat aplikasi yang sudah selesai.

b2f84ff91b0e1396.pngLuncurkan Android Studio.

  1. Jika Anda tidak memiliki project yang terbuka, pilih Start a new Flutter app dari halaman sambutan. Atau, pilih File > New > New Flutter Project.
  2. Pilih Flutter Application sebagai jenis project, lalu klik Next.
  3. Verifikasi bahwa jalur Flutter SDK menentukan lokasi SDK. (Pilih Install SDK jika kolom teks kosong.)
  4. Masukkan friendly_chat sebagai nama project, lalu klik Next.
  5. Gunakan nama paket default yang disarankan oleh Android Studio, lalu klik Next.
  6. Klik Finish.
  7. Tunggu Android Studio menginstal SDK dan membuat project tersebut.

b2f84ff91b0e1396.pngAtau, buat aplikasi Flutter pada command line.

$ flutter create friendly_chat
$ cd friendly_chat
$ dart migrate --apply-changes
$ flutter run

Terjadi masalah?

Lihat halaman Test drive untuk informasi selengkapnya terkait cara membuat aplikasi dengan template sederhana. Atau, gunakan kode di link berikut untuk membuatnya kembali normal.

Pada bagian ini, Anda akan mulai memodifikasi aplikasi contoh bawaan, untuk membuatnya menjadi aplikasi chat. Tujuannya adalah untuk menggunakan Flutter untuk mem-build FriendlyChat, aplikasi chat sederhana dan dapat diperluas dengan fitur berikut:

  • Aplikasi menampilkan pesan teks secara real time
  • Pengguna dapat memasukkan pesan string teks, dan mengirimkannya baik dengan menekan tombol Return atauSend.
  • UI dapat berjalan di perangkat Android dan iOS, serta web.

Coba aplikasi yang sudah selesai di DartPad!

Membuat scaffold aplikasi utama

Elemen pertama yang Anda tambahkan adalah panel aplikasi sederhana yang menampilkan judul statis aplikasi. Saat Anda melalui bagian codelab berikutnya, secara bertahap Anda akan menambahkan elemen UI yang lebih responsif dan stateful ke aplikasi.

File main.dart berada di bawah direktori lib di project Flutter Anda, dan berisi fungsi main() yang memulai eksekusi aplikasi.

b2f84ff91b0e1396.pngGanti semua kode dalam main.dart dengan kode berikut:

import 'package:flutter/material.dart';

void main() {
  runApp(
    MaterialApp(
      title: 'FriendlyChat',
      home: Scaffold(
        appBar: AppBar(
          title: Text('FriendlyChat'),
        ),
      ),
    ),
  );
}

cf1e10b838bf60ee.png Pengamatan

  • Semua program Dart, baik aplikasi command line, aplikasi AngularDart, maupun aplikasi Flutter, dimulai dengan fungsi main().
  • Definisi fungsi main() dan runApp() sama seperti pada aplikasi yang dibuat secara otomatis.
  • Fungsi runApp() menggunakan Widget sebagai argumennya, yang diperluas oleh flutter Flutter dan menampilkannya ke layar pada saat runtime.
  • Aplikasi chat ini menggunakan elemen Desain Material di UI, sehingga objek MaterialApp dibuat dan diteruskan ke fungsi runApp(). Widget MaterialApp menjadi root dari hierarki widget aplikasi Anda.
  • Argumen home menentukan layar default yang dilihat pengguna di aplikasi Anda. Dalam hal ini, widget ini terdiri dari widget Scaffold yang memiliki AppBar sederhana sebagai widget turunannya. Hal ini umum terjadi pada aplikasi Material

b2f84ff91b0e1396.pngJalankan aplikasi dengan mengklik ikon Run 6869d41b089cc745.png di editor. Aplikasi memerlukan beberapa waktu untuk dimuat saat pertama kali dijalankan. Aplikasi akan lebih cepat di langkah-langkah berikutnya.

febbb7a3b70462b7.png

Anda akan melihat sesuatu seperti berikut:

Pixel 3XL

iPhone 11

Membuat layar chat

Untuk meletakkan dasar komponen interaktif, Anda akan membagi aplikasi sederhana menjadi dua subclasses widget yang berbeda: widget FriendlyChatApp level root yang tidak pernah berubah dan widget ChatScreen turunan yang di-build ulang saat pesan dikirim dan perubahan status internal. Untuk saat ini, kedua class ini dapat memperluas StatelessWidget. Selanjutnya, Anda akan mengubah ChatScreen menjadi widget stateful. Dengan begitu, Anda dapat mengubah statusnya sesuai kebutuhan.

b2f84ff91b0e1396.pngBuat widget FriendlyChatApp:

  1. Di dalam main(), tempatkan kursor di depan M di MaterialApp.
  2. Klik kanan, lalu pilih Refactor > Extract > Extract Flutter widget.

a133a9648f86738.png

  1. Masukkan FriendlyChatApp ke dialog EkstrakWidget, lalu klik tombol Refactor. Kode MaterialApp ditempatkan di widget stateless baru yang disebut FriendlyChatApp, dan main() diupdate untuk memanggil class tersebut saat memanggil fungsi runApp().
  2. Pilih blok teks setelah home:. Dimulai dengan Scaffold( dan diakhiri dengan tanda kurung penutup Scaffold, ). Jangan sertakan koma akhir.
  3. Mulailah mengetik ChatScreen, dan pilih ChatScreen() dari pop-up. (Pilih entri ChatScreen yang ditandai dengan tanda sama dengan di dalam lingkaran kuning. Ini akan memberi Anda class dengan tanda kurung kosong, bukan konstanta.)

b2f84ff91b0e1396.png Buat widget stateless, ChatScreen:

  1. Di bawah class FriendlyChatApp, sekitar baris 27, mulai ketik stless. Editor akan menanyakan apakah Anda ingin membuat widget Stateless. Tekan Return untuk menyetujui. Kode boilerplate muncul, dan kursor diposisikan untuk Anda memasukkan nama widget stateless Anda.
  2. Masukkan ChatScreen.

b2f84ff91b0e1396.pngUpdate widget ChatScreen:

  1. Di dalam widget ChatScreen, pilih Container, dan mulai ketik Scaffold. Pilih Scaffold dari pop-up.
  2. Kursor harus ditempatkan di dalam tanda kurung. Tekan Return untuk memulai baris baru.
  3. Mulai ketik appBar, dan pilih appBar: dari pop-up.
  4. Setelah appBar:, mulai ketik AppBar, dan pilih class AppBar dari pop-up.
  5. Dalam tanda kurung, mulai ketik title, dan pilih title: dari pop-up.
  6. Setelah title:, mulai ketik Text, dan pilih class Teks.
  7. Kode boilerplate untuk Text berisi kata data. Hapus koma pertama setelah data. Pilih data, dan ganti dengan 'FriendlyChat'. (Dart mendukung tanda kutip tunggal atau ganda, tetapi lebih memilih tanda kutip tunggal kecuali teks sudah berisi tanda kutip tunggal.)

Lihat di pojok kanan atas panel kode. Jika Anda melihat tanda centang hijau, artinya kode Anda lulus analisis. Selamat!

cf1e10b838bf60ee.png Pengamatan

Langkah ini memperkenalkan beberapa konsep utama framework Flutter:

  • Anda mendeskripsikan bagian antarmuka pengguna yang diwakili oleh widget dalam metode build(). Framework ini memanggil metode build() untuk FriendlyChatApp dan ChatScreen saat menyisipkan widget ini ke dalam hierarki widget dan saat dependensinya berubah.
  • @override adalah anotasi Dart yang menunjukkan bahwa metode yang diberi tag mengganti metode superclass.
  • Beberapa widget, seperti Scaffold dan AppBar, dikhususkan untuk aplikasi Desain Material. Widget lainnya, seperti Text, bersifat umum dan dapat digunakan di aplikasi apa pun. Widget dari berbagai pustaka dalam framework Flutter kompatibel dan dapat berfungsi bersama dalam satu aplikasi.
  • Menyederhanakan metode main() memungkinkan hot reload karena pemuatan ulang tidak menjalankan ulang main().

b2f84ff91b0e1396.pngKlik tombol 48583acd5d1a5e12.png hot reload untuk melihat perubahan hampir seketika. Setelah membagi UI menjadi beberapa class terpisah dan mengubah widget root, Anda tidak akan melihat perubahan yang terlihat di UI.

Terjadi masalah?

Jika aplikasi tidak berjalan dengan baik, cari apakah ada salah ketik. Jika perlu, gunakan kode di link berikut untuk membuat aplikasi normal kembali.

Di bagian ini, Anda akan mempelajari cara mem-build kontrol pengguna yang memungkinkan pengguna memasukkan dan mengirim pesan chat.

64fd9c97437a7461.png

Di perangkat, mengklik kolom teks akan menampilkan keyboard virtual. Pengguna dapat mengirim pesan chat dengan mengetik string yang tidak kosong dan menekan tombol Return di keyboard virtual. Atau, pengguna dapat mengirim pesan yang diketik dengan menekan tombol Send grafis di samping kolom input.

Untuk saat ini, UI untuk menulis pesan berada di bagian atas layar chat, tetapi setelah menambahkan UI untuk menampilkan pesan di langkah berikutnya, Anda memindahkannya ke bagian bawah layar chat.

Menambahkan kolom input teks interaktif

Framework Flutter menyediakan widget Desain Material yang disebut TextField. Ini adalah StatefulWidget (widget yang memiliki status yang dapat diubah) dengan properti untuk menyesuaikan perilaku kolom input. State adalah informasi yang dapat dibaca secara sinkron saat widget di-build dan mungkin berubah selama masa pakai widget. Menambahkan widget stateful pertama ke aplikasi FriendlyChat memerlukan beberapa modifikasi.

b2f84ff91b0e1396.png Ubah class ChatScreen menjadi stateful:

  1. Pilih ChatScreen di baris class ChatScreen extends StatelessWidget.
  2. Tekan Option+Return (macOS) atau Alt+Enter (Linux dan Windows) untuk memunculkan menu.
  3. Dari menu, pilih Convert to StatefulWidget. Class secara otomatis diperbarui dengan kode boilerplate untuk widget stateful, termasuk class _ChatScreenState baru untuk mengelola status.

Untuk mengelola interaksi dengan kolom teks, Anda dapat menggunakan objek TextEditingController untuk membaca konten kolom input dan menghapus kolom setelah pesan chat dikirim.

b2f84ff91b0e1396.png Tambahkan TextEditingController ke _ChatScreenState.

Tambahkan baris berikut sebagai baris pertama di class _ChatScreenState:

final _textController = TextEditingController();

Setelah aplikasi Anda memiliki kemampuan untuk mengelola status, Anda dapat mem-build class _ChatScreenState dengan kolom input dan tombol Send.

b2f84ff91b0e1396.pngTambahkan fungsi _buildTextComposer ke _ChatScreenState:

  Widget _buildTextComposer() {
    return  Container(
        margin: EdgeInsets.symmetric(horizontal: 8.0),
      child: TextField(
        controller: _textController,
        onSubmitted: _handleSubmitted,
        decoration: InputDecoration.collapsed(
            hintText: 'Send a message'),
      ),
    );
  }

cf1e10b838bf60ee.png Pengamatan

  • Di Flutter, data stateful untuk widget diringkas dalam objek State. Objek State kemudian dikaitkan dengan widget yang memperluas class StatefulWidget.
  • Kode di atas menentukan metode pribadi yang disebut _buildTextComposer() yang menampilkan widget Container dengan widget TextField yang dikonfigurasi.
  • Widget Container menambahkan margin horizontal di antara tepi layar dan setiap sisi kolom input.
  • Unit yang diteruskan ke EdgeInsets.symmetric adalah piksel logis yang diterjemahkan menjadi jumlah piksel fisik tertentu, bergantung pada rasio piksel perangkat. Anda mungkin sudah tidak asing dengan istilah yang setara untuk Android (piksel kepadatan mandiri) atau untuk iOS (poin).
  • Properti onSubmitted menyediakan metode callback pribadi, _handleSubmitted(). Pada awalnya, metode ini hanya menghapus kolom, tetapi kemudian Anda perluas untuk mengirim pesan chat.
  • TextField dengan TextEditingController memberi Anda kontrol atas kolom teks. Pengontrol ini akan menghapus kolom dan membaca nilainya.

b2f84ff91b0e1396.png Tambahkan fungsi _handleSubmitted ke _ChatScreenState untuk menghapus pengontrol teks:

  void _handleSubmitted(String text) {
    _textController.clear();
  }

Menambahkan widget penulis teks

b2f84ff91b0e1396.png Perbarui metode build() untuk _ChatScreenState.

Setelah baris appBar: AppBar(...), tambahkan properti body::

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('FriendlyChat')),
      body: _buildTextComposer(),    // NEW
    );
  }

cf1e10b838bf60ee.png Pengamatan

  • Metode _buildTextComposer menampilkan widget yang mengenkapsulasi kolom input teks.
  • Menambahkan _buildTextComposer ke properti body menyebabkan aplikasi menampilkan kontrol pengguna input teks.

b2f84ff91b0e1396.pngHot reload aplikasi. Anda akan melihat layar yang tampak seperti berikut:

Pixel 3XL

iPhone 11

Menambahkan tombol Kirim yang responsif

Selanjutnya, tambahkan tombol Kirim di sebelah kanan kolom teks. Ini melibatkan penambahan lebih banyak struktur ke layout.

b2f84ff91b0e1396.png Pada fungsi _buildTextComposer, masukkan TextField di dalam Row:

  1. Pilih TextField di _buildTextComposer.
  2. Tekan Option+Return (macOS) atau Alt+Enter (Linux dan Windows) untuk memunculkan menu, lalu pilih Wrap with widget. Widget baru ditambahkan yang menggabungkan TextField. Nama placeholder dipilih, dan IDE menunggu Anda memasukkan nama placeholder baru.
  3. Mulai ketik Row, dan pilih Row dari daftar yang muncul. Pop-up akan muncul yang berisi definisi untuk konstruktor Row. Properti child memiliki batas merah, dan penganalisis memberi tahu Anda bahwa Anda tidak memiliki properti children yang diperlukan.
  4. Arahkan kursor ke atas child dan pop-up akan muncul. Di pop-up, perintah akan menanyakan apakah Anda ingin mengubah properti ke children. Pilih opsi tersebut.
  5. Properti children mengambil daftar, bukan satu widget. (Saat ini, hanya ada satu item dalam daftar, tetapi akan segera ditambahkan.) Konversi widget menjadi daftar dengan mengetikkan kurung siku kiri ([) setelah teks children:. Editor juga memberikan kurung tutup. Hapus tanda kurung tutup. Beberapa baris ke bawah, tepat sebelum tanda kurung kanan yang menutup baris, ketik tanda kurung siku kanan diikuti dengan koma (],). Penganalisis sekarang akan menampilkan tanda centang hijau.
  6. Kode sudah benar, tetapi tidak diformat dengan baik. Klik kanan di panel kode, lalu pilih Reformat Code with dartfmt.

b2f84ff91b0e1396.png Gabungkan TextField ke dalam Flexible:

  1. Pilih Row.
  2. Tekan Option+Return (macOS) atau Alt+Enter (Linux dan Windows) untuk memunculkan menu, lalu pilih Wrap with widget. Widget baru ditambahkan yang menggabungkan TextField. Nama placeholder dipilih, dan IDE menunggu Anda memasukkan nama placeholder baru.
  3. Mulai ketik Flexible, dan pilih Flexible dari daftar yang muncul. Pop-up akan muncul yang berisi definisi untuk konstruktor Row.
Widget _buildTextComposer() {
  return  Container(
    margin: EdgeInsets.symmetric(horizontal: 8.0),
    child:  Row(                             // NEW
      children: [                            // NEW
         Flexible(                           // NEW
          child:  TextField(
            controller: _textController,
            onSubmitted: _handleSubmitted,
            decoration:  InputDecoration.collapsed(
                hintText: 'Send a message'),
          ),
        ),                                    // NEW
      ],                                      // NEW
    ),                                        // NEW
  );
}

cf1e10b838bf60ee.png Pengamatan

  • Menggunakan Row memungkinkan Anda untuk menempatkan tombol Kirim di samping kolom input.
  • Menggabungkan TextField dalam widget Flexible memberi tahu Row untuk menyesuaikan ukuran kolom teks secara otomatis agar menggunakan ruang tersisa yang tidak digunakan tombolnya.
  • Penambahan koma setelah kurung siku kanan memberi tahu pemformat cara memformat kode.

Selanjutnya, tambahkan tombol Kirim. Ini adalah aplikasi Material, jadi gunakan ikon Material yang sesuai 2de111ba4b057a1e.png:

b2f84ff91b0e1396.pngTambahkan tombol Kirim ke Row.

Tombol Kirim menjadi item kedua dalam daftar Row.

  1. Posisikan kursor di akhir tanda kurung siku tutup Flexible dan koma widget, lalu tekan Return untuk memulai baris baru.
  2. Mulai ketik Container, dan pilih Container dari pop-up. Kursor diposisikan di dalam tanda kurung container. Tekan Return untuk memulai baris baru.
  3. Tambahkan baris kode berikut ke container:
margin: EdgeInsets.symmetric(horizontal: 4.0),
child: IconButton(
    icon: const Icon(Icons.send),
    onPressed: () => _handleSubmitted(_textController.text)),

cf1e10b838bf60ee.png Pengamatan

  • Class IconButton menampilkan tombol Kirim.
  • Properti icon menentukan konstanta Icons.send dari library Material untuk membuat instance Icon baru.
  • Menempatkan IconButton di dalam widget Container memungkinkan Anda menyesuaikan jarak margin tombol sehingga lebih cocok secara visual di samping kolom input.
  • Properti onPressed menggunakan fungsi anonim untuk memanggil metode _handleSubmitted() dan meneruskan konten pesan menggunakan _textController.
  • Di Dart, sintaks panah ( => expression) biasanya digunakan untuk mendeklarasikan fungsi. Ini adalah singkatan dari { return expression; } dan hanya digunakan untuk fungsi satu baris. Untuk ringkasan dukungan fungsi Dart, termasuk fungsi anonim dan bertingkat, lihat Tur Bahasa Dart.

b2f84ff91b0e1396.png Hot reload aplikasi untuk melihat tombol Kirim:

Pixel 3XL

iPhone 11

Warna tombol berwarna hitam, yang berasal dari tema Desain Material default. Untuk memberikan warna aksen pada ikon di aplikasi Anda, teruskan argumen warna ke IconButton, atau terapkan tema lain.

b2f84ff91b0e1396.png Dalam _buildTextComposer(), gabungkan Container ke IconTheme:.

  1. Pilih Container di bagian atas fungsi _buildTextComposer().
  2. Tekan Option+Return (macOS) atau Alt+Enter (Linux dan Windows) untuk memunculkan menu, lalu pilih Wrap with widget. Widget baru ditambahkan yang menggabungkan Container. Nama placeholder dipilih, dan IDE menunggu Anda memasukkan nama placeholder baru.
  3. Mulai ketik IconTheme, dan pilih IconTheme dari daftar. Properti child dikelilingi oleh kotak merah, dan penganalisis memberi tahu Anda bahwa properti data diperlukan.
  4. Tambahkan properti data:
return IconTheme(
  data: IconThemeData(color: Theme.of(context).accentColor), // NEW
  child: Container(

cf1e10b838bf60ee.png Pengamatan

  • Ikon mewarisi warna, keburaman, dan ukurannya dari widget IconTheme, yang menggunakan objek IconThemeData untuk menentukan karakteristik ini.
  • Properti data IconTheme menentukan objek ThemeData untuk tema saat ini. Ini memberi tombol (dan ikon lainnya dalam bagian hierarki widget ini) warna aksen dari tema saat ini.
  • Objek BuildContext adalah tuas ke lokasi widget di hierarki widget aplikasi Anda. Setiap widget memiliki BuildContext miliknya sendiri, yang menjadi induk dari widget yang ditampilkan oleh fungsi StatelessWidget.build atau State.build. Hal ini berarti _buildTextComposer() dapat mengakses objek BuildContext dari objek State yang dienkapsulasi. Anda tidak perlu meneruskan konteks ke metode secara eksplisit.

b2f84ff91b0e1396.png Hot reload aplikasi. Tombol Kirim sekarang akan berwarna biru:

Pixel 3XL

iPhone 11

Terjadi masalah?

Jika aplikasi tidak berjalan dengan baik, cari apakah ada salah ketik. Jika perlu, gunakan kode di link berikut untuk membuat aplikasi normal kembali.

e57d18c5bb8f2ac7.pngAnda telah menemukan hal yang istimewa!

Ada beberapa cara untuk men-debug aplikasi Anda. Anda dapat menggunakan IDE langsung untuk menetapkan titik henti sementara, atau Anda dapat menggunakan Dart DevTools (berbeda dengan Chrome DevTools). Codelab ini menunjukkan cara menetapkan titik henti sementara menggunakan Android Studio dan IntelliJ. Jika Anda menggunakan editor lain, seperti VS Code, gunakan DevTools untuk men-debug. Untuk pengantar umum mengenai Dart DevTools, lihat Langkah 2.5 Write your first Flutter app on the web.

Android Studio dan IntelliJ IDE memungkinkan Anda men-debug aplikasi Flutter yang berjalan di emulator, simulator, atau perangkat. Dengan editor ini, Anda dapat:

  • Memilih perangkat atau simulator untuk men-debug aplikasi Anda.
  • Melihat pesan konsol.
  • Menetapkan titik henti sementara di kode Anda.
  • Memeriksa variabel dan mengevaluasi ekspresi pada waktu proses.

Editor Android Studio dan IntelliJ menampilkan log sistem saat aplikasi Anda berjalan, dan menyediakan UI Debugger untuk bekerja dengan titik henti sementara dan mengontrol alur eksekusi.

6ea611ca007eb43c.png

Bekerja dengan titik henti sementara

b2f84ff91b0e1396.pngDebug aplikasi Flutter Anda menggunakan titik henti sementara:

  1. Buka file sumber tempat Anda ingin menetapkan titik henti sementara.
  2. Temukan baris tempat Anda ingin menetapkan titik henti sementara, klik baris tersebut, lalu pilih Run > Toggle Line Breakpoint. Atau, Anda dapat mengklik di gutter (di sebelah kanan nomor baris) untuk mengalihkan titik henti sementara.
  3. Jika Anda tidak berjalan dalam mode debug, hentikan aplikasi.
  4. Mulai ulang aplikasi menggunakan Run > Debug, atau dengan mengklik tombol Run debug di UI.

Editor meluncurkan UI Debugger dan menjeda eksekusi aplikasi Anda saat mencapai titik henti sementara. Kemudian, Anda dapat menggunakan kontrol di UI Debugger untuk mengidentifikasi penyebab error.

Berlatihlah menggunakan debugger dengan menetapkan titik henti sementara pada metode build() di aplikasi FriendlyChat, lalu jalankan dan debug aplikasi. Anda dapat memeriksa frame stack untuk melihat histori panggilan metode oleh aplikasi Anda.

Dengan memiliki layar dan scaffolding aplikasi dasar, sekarang Anda sudah siap untuk menentukan area tempat pesan chat ditampilkan.

de23b9bb7bf84592.png

Menerapkan daftar pesan chat

Di bagian ini, Anda membuat widget yang menampilkan pesan chat menggunakan komposisi (membuat dan menggabungkan beberapa widget yang lebih kecil). Mulai dengan widget yang mewakili satu pesan chat. Kemudian, sarangkan widget tersebut di daftar yang dapat di-scroll induk. Terakhir, sarangkan daftar yang dapat di-scroll di scaffold aplikasi dasar.

b2f84ff91b0e1396.pngTambahkan widget stateless ChatMessage:

  1. Posisikan kursor setelah class FriendlyChatApp dan mulai mengetik stless. (Urutan class tidak penting, tetapi urutan ini mempermudah untuk membandingkan kode Anda dengan solusinya.)
  2. Masukkan ChatMessage untuk nama class.

b2f84ff91b0e1396.pngTambahkan Row ke metode build() untuk ChatMessage:

  1. Posisikan kursor di dalam tanda kurung di return Container(), dan tekan Return untuk memulai baris baru.
  2. Tambahkan properti margin:
margin: EdgeInsets.symmetric(vertical: 10.0),
  1. Turunan Container' akan berupa Row. Daftar Row berisi dua widget: avatar dan kolom teks.
return Container(
  child: Row(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: [
      Container(
        margin: const EdgeInsets.only(right: 16.0),
        child: CircleAvatar(child: Text(_name[0])),
      ),
      Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(_name, style: Theme.of(context).textTheme.headline4),
          Container(
            margin: EdgeInsets.only(top: 5.0),
            child: Text(text),
          ),
        ],
      ),
    ],
  ),
);
  1. Tambahkan variabel text dan konstruktor ke atas ChatMessage:
class ChatMessage extends StatelessWidget {
  ChatMessage({required this.text}); // NEW
  final String text;                 // NEW

Di titik ini, penganalisis hanya akan mengeluh tentang _name yang tidak ditentukan. Anda akan memperbaikinya nanti.

b2f84ff91b0e1396.pngTentukan variabel _name.

Tentukan variabel _name seperti yang ditampilkan, menggantikan Your Name dengan nama Anda. Gunakan variabel untuk memberi label setiap pesan chat dengan nama pengirim. Di codelab ini, Anda melakukan hard code pada nilai agar lebih mudah, tetapi sebagian besar aplikasi mengambil nama pengirim melalui autentikasi. Setelah fungsi main(), tambahkan baris berikut:

String _name = 'Your Name';

cf1e10b838bf60ee.png Pengamatan

  • Metode build() untuk ChatMessage mengirimkan Row yang menampilkan avatar grafis sederhana untuk mewakili pengguna yang mengirimkan pesan chat, widget Column berisi nama pengirim, dan teks pesan.
  • CircleAvatar dipersonalisasi dengan memberinya label dengan inisial nama pengguna dengan meneruskan karakter pertama nilai variabel _name ke widget Text turunan.
  • Parameter crossAxisAlignment menentukan CrossAxisAlignment.start di konstruktor Row untuk memosisikan avatar dan pesan relatif terhadap widget induknya. Untuk avatar, induknya adalah widget Row yang sumbu utamanya adalah horizontal, sehingga CrossAxisAlignment.start memberinya posisi tertinggi di sepanjang sumbu vertikal. Untuk pesan, induknya adalah widget Column yang sumbu utamanya adalah vertikal, sehingga CrossAxisAlignment.start meratakan teks ke posisi paling kiri di sepanjang sumbu horizontal.
  • Di samping avatar, dua widget Text diratakan secara vertikal untuk menampilkan nama pengirim di atas dan teks pesan di bawah.
  • Theme.of(context) menyediakan objek ThemeData Flutter default untuk aplikasi. Di langkah berikutnya, Anda mengganti tema default ini untuk memberi gaya pada aplikasi Anda secara berbeda untuk Android dan iOS.
  • Properti textTheme ThemeData memberi Anda akses ke gaya logis Desain Material untuk teks seperti headline4, sehingga Anda dapat menghindari melakukan hard code pada ukuran font dan atribut teks lainnya. Dalam contoh ini, nama pengirim diberi gaya untuk membuatnya lebih besar dari teks pesan.

b2f84ff91b0e1396.pngLakukan hot reload pada aplikasi.

Ketik pesan ke kolom teks. Tekan tombol Kirim untuk menghapus pesan. Ketik pesan panjang ke kolom teks untuk melihat apa yang terjadi ketika kolom teks meluap. Kemudian, di langkah 9, gabungkan kolom dalam widget Expanded untuk membuat gabungan widget Text.

Menerapkan daftar pesan chat di UI

Penyempurnaan berikutnya adalah mendapatkan daftar pesan chat dan menampilkannya di UI. Anda ingin daftar ini dapat di-scroll sehingga pengguna dapat melihat histori pesan. Daftar tersebut juga harus menyajikan pesan dalam urutan kronologis, dengan pesan terbaru ditampilkan di baris paling bawah dari daftar yang terlihat.

b2f84ff91b0e1396.pngTambahkan daftar _messages ke _ChatScreenState.

Di definisi _ChatScreenState, tambahkan anggota List yang disebut _messages untuk mewakili setiap pesan chat:

class _ChatScreenState extends State<ChatScreen> {
  final List<ChatMessage> _messages = [];      // NEW
  final _textController = TextEditingController();

b2f84ff91b0e1396.pngUbah metode _handleSubmitted() di _ChatScreenState.

Saat pengguna mengirimkan pesan chat dari kolom teks, aplikasi harus menambahkan pesan baru tersebut ke daftar pesan. Ubah metode _handleSubmitted() untuk menerapkan perilaku ini:

void _handleSubmitted(String text) {
  _textController.clear();
  ChatMessage message = ChatMessage(    //NEW
    text: text,                         //NEW
  );                                    //NEW
  setState(() {                         //NEW
    _messages.insert(0, message);       //NEW
  });                                   //NEW
 }

b2f84ff91b0e1396.pngLetakkan fokus kembali di kolom teks setelah konten dikirimkan.

  1. Tambahkan FocusNode ke _ChatScreenState:
class _ChatScreenState extends State<ChatScreen> {
  final List<ChatMessage> _messages = [];
  final _textController = TextEditingController();
  final FocusNode _focusNode = FocusNode();    // NEW
  1. Tambahkan properti focusNode ke TextField di _buildTextComposer():
child: TextField(
  controller: _textController,
  onSubmitted: _handleSubmitted,
  decoration: InputDecoration.collapsed(hintText: 'Send a message'),
  focusNode: _focusNode,  // NEW
),
  1. Di _handleSubmitted(), setelah panggilan ke setState(), minta fokus pada TextField:
    setState(() {
      _messages.insert(0, message);
    });
    _focusNode.requestFocus();  // NEW

cf1e10b838bf60ee.png Pengamatan

  • Setiap item dalam daftar adalah instance ChatMessage.
  • Daftar ini diinisialisasi agar kosong.
  • Memanggil setState() untuk mengubah _messages memberi tahu framework bahwa bagian hierarki widget ini berubah dan perlu mem-build ulang UI. Hanya operasi sinkron yang akan dilakukan di setState() karena jika tidak, framework dapat mem-build ulang widget sebelum operasi selesai.
  • Secara umum, Anda dapat memanggil setState() dengan penutupan tertutup setelah beberapa data pribadi diubah di luar pemanggilan metode ini. Namun, mengupdate data di dalam penutupan setState lebih diutamakan, sehingga Anda tidak lupa untuk memanggilnya.

b2f84ff91b0e1396.pngLakukan hot reload pada aplikasi.

Masukkan teks ke kolom teks, lalu tekan Return. Kolom teks sekali lagi memiliki fokus.

Menempatkan daftar pesan

Anda sekarang sudah siap untuk menampilkan daftar pesan chat. Dapatkan widget ChatMessage dari daftar _messages, dan letakkan di widget ListView, untuk daftar yang dapat di-scroll.

b2f84ff91b0e1396.pngDi metode build() untuk _ChatScreenState, tambahkan ListView di dalam Column:

Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(title: Text ('FriendlyChat')),
    body: Column(                                            // MODIFIED
      children: [                                            // NEW
        Flexible(                                            // NEW
          child: ListView.builder(                           // NEW
            padding: EdgeInsets.all(8.0),                    // NEW
            reverse: true,                                   // NEW
            itemBuilder: (_, int index) => _messages[index], // NEW
            itemCount: _messages.length,                     // NEW
          ),                                                 // NEW
        ),                                                   // NEW
        Divider(height: 1.0),                                // NEW
        Container(                                           // NEW
          decoration: BoxDecoration(
            color: Theme.of(context).cardColor),             // NEW
          child: _buildTextComposer(),                       // MODIFIED
        ),                                                   // NEW
      ],                                                     // NEW
    ),                                                       // NEW
  );
}

cf1e10b838bf60ee.png Pengamatan

  • Metode factory ListView.builder membuat daftar sesuai permintaan dengan memberikan fungsi yang dipanggil sekali per item di daftar. Fungsi ini menampilkan widget baru di setiap panggilan. Builder juga otomatis mendeteksi mutasi parameter children-nya dan memulai build ulang.
  • Parameter yang diteruskan ke konstruktor ListView.builder menyesuaikan isi dan tampilan daftar:
  • padding membuat spasi kosong di sekitar teks.
  • itemCount menentukan jumlah pesan dalam daftar.
  • itemBuilder menyediakan fungsi yang mem-build setiap widget di [index]. Karena Anda tidak memerlukan konteks build saat ini, Anda dapat mengabaikan argumen pertama IndexedWidgetBuilder. Memberi nama argumen dengan garis bawah (_) dan bukan lainnya adalah konvensi yang menunjukkan bahwa argumen tidak akan digunakan.
  • Properti body dari widget Scaffold sekarang berisi daftar pesan masuk serta kolom input dan tombol Kirim. Tata letak menggunakan widget berikut:
  • Column: Mengatur turunan langsungnya secara vertikal. Widget Column mengambil daftar widget turunan (sama seperti Row) yang menjadi daftar scroll dan and a baris untuk kolom input.
  • Flexible, sebagai induk ListView: Memberi tahu framework agar daftar pesan yang diterima meluas untuk mengisi tinggi Column, sementara TextField masih berukuran tetap.
  • Divider: Menggambar garis horizontal antara UI untuk menampilkan pesan dan kolom input teks untuk menulis pesan.
  • Container, sebagai induk dari komposer teks: Menentukan gambar latar belakang, padding, margin, dan detail tata letak umum lainnya.
  • decoration: Membuat objek BoxDecoration baru yang menentukan warna latar belakang. Dalam hal ini, Anda menggunakan cardColor yang ditentukan oleh objek ThemeData tema default. Hal ini memberikan latar belakang yang berbeda dari daftar pesan kepada UI untuk menulis pesan.

b2f84ff91b0e1396.pngLakukan hot reload aplikasi. Anda akan melihat layar yang terlihat seperti berikut:

Pixel 3XL

iPhone 11

b2f84ff91b0e1396.pngCoba kirim beberapa pesan chat menggunakan UI untuk menulis dan menampilkan yang baru saja Anda buat.

Pixel 3XL

iPhone 11

Terjadi masalah?

Jika aplikasi tidak berjalan dengan baik, cari apakah ada salah ketik. Jika perlu, gunakan kode di link berikut untuk membuat aplikasi normal kembali.

Anda dapat menambahkan animasi ke widget Anda untuk membuat pengalaman pengguna aplikasi Anda menjadi lebih mulus dan intuitif. Di bagian ini, Anda mempelajari cara menambahkan efek animasi dasar ke daftar pesan chat Anda.

Ketika pengguna mengirimkan pesan chat baru, Anda menganimasikan pesan secara vertikal dari bagian bawah layar, bukan hanya menampilkannya di daftar pesan.

Animasi di Flutter digabungkan sebagai objek Animation yang berisi nilai yang diketik dan status (seperti forward, reverse, completed, dan dismissed). Anda dapat melampirkan objek animasi ke widget atau memproses perubahan pada objek animasi. Berdasarkan perubahan pada properti objek animasi, framework dapat mengubah cara widget Anda muncul dan mem-build ulang hierarki widget.

Menentukan pengontrol animasi

Gunakan class AnimationController untuk menentukan cara animasi seharusnya berjalan. AnimationController memungkinkan Anda menentukan karakteristik penting animasi, seperti durasinya dan arah pemutarannya (maju atau terbalik).

b2f84ff91b0e1396.pngPerbarui definisi class _ChatScreenState untuk menyertakan TickerProviderStateMixin:

class _ChatScreenState extends State<ChatScreen> with TickerProviderStateMixin {   // MODIFIED
  final List<ChatMessage> _messages = [];
  final _textController = TextEditingController();
  final FocusNode _focusNode = FocusNode();
  ...

b2f84ff91b0e1396.pngDi definisi class ChatMessage, tambahkan variabel untuk menyimpan pengontrol animasi:

class ChatMessage extends StatelessWidget {
  ChatMessage({required this.text, required this.animationController}); // MODIFIED
  final String text;
  final AnimationController animationController;      // NEW
  ...

b2f84ff91b0e1396.pngTambahkan pengontrol animasi ke metode _handleSubmitted():

void _handleSubmitted(String text) {
  _textController.clear();
  var message = ChatMessage(
    text: text,
    animationController: AnimationController(      // NEW
      duration: const Duration(milliseconds: 700), // NEW
      vsync: this,                                 // NEW
    ),                                             // NEW
  );                                               // NEW
  setState(() {
    _messages.insert(0, message);
  });
  _focusNode.requestFocus();
  message.animationController.forward();           // NEW
}

cf1e10b838bf60ee.png Pengamatan

  • AnimationController menentukan durasi runtime animasi menjadi 700 milidetik. (Durasi yang lebih panjang ini memperlambat efek animasi, sehingga Anda dapat melihat transisi yang terjadi dengan lebih lambat. Dalam praktiknya, Anda mungkin ingin menetapkan durasi yang lebih singkat saat menjalankan aplikasi.)
  • Pengontrol animasi dilampirkan ke instance ChatMessage baru, dan menentukan bahwa animasi harus diputar maju setiap kali pesan ditambahkan ke daftar chat.
  • Saat membuat AnimationController, Anda harus meneruskan argumen vsync ke sana. vsync adalah sumber heartbeats (Ticker) yang mendorong animasi. Contoh ini menggunakan _ChatScreenState sebagai vsync, sehingga menambahkan mixin TickerProviderStateMixin ke definisi class _ChatScreenState.
  • Di Dart, mixin memungkinkan isi class digunakan kembali di beberapa hierarki class. Untuk informasi selengkapnya, lihat Adding features to a class: mixins, bagian di Dart Language Tour.

Menambahkan widget SizeTransition

Menambahkan widget SizeTransition ke animasi memiliki efek animasi ClipRect yang semakin mengekspos teks saat bergeser.

b2f84ff91b0e1396.pngTambahkan widget SizeTransition ke metode build() untuk ChatMessage:

  1. Di metode build() untuk ChatMessage, pilih instance Container pertama.
  2. Tekan Option+Return (macOS) atau Alt+Enter (Linux dan Windows) untuk memunculkan menu, lalu pilih Wrap with widget.
  3. Masukkan SizeTransition. Kotak merah muncul di sekitar properti child:. Hal ini menunjukkan bahwa properti wajib tidak ada dalam class widget. Arahkan kursor ke SizeTransition, dan tooltip menunjukkan bahwa sizeFactor diperlukan dan menawarkan untuk membuatnya. Pilih opsi tersebut, dan properti akan muncul dengan nilai null.
  4. Ganti null dengan instance CurvedAnimation. Tindakan ini menambahkan kode boilerplate untuk dua properti: parent (wajib) dan curve.
  5. Untuk properti parent, ganti null dengan animationController.
  6. Untuk properti curve, ganti null dengan Curves.easeOut, salah satu konstanta dari class Curves.
  7. Tambahkan baris setelah sizeFactor (tetapi di level yang sama), dan masukkan properti axisAlignment ke SizeTransition, dengan nilai 0.0.
@override
Widget build(BuildContext context) {
  return SizeTransition(             // NEW
    sizeFactor:                      // NEW
        CurvedAnimation(parent: animationController, curve: Curves.easeOut),  // NEW
    axisAlignment: 0.0,              // NEW
    child: Container(                // MODIFIED
    ...

cf1e10b838bf60ee.png Pengamatan

  • Objek CurvedAnimation, bersama dengan class SizeTransition, menghasilkan efek animasi melambat. Efek melambat ini menyebabkan pesan bergeser ke atas dengan cepat di awal animasi dan melambat sampai berhenti.
  • Widget SizeTransition berfungsi sebagai ClipRect animasi yang memperlihatkan lebih banyak teks saat bergeser.

Membuang animasi

Untuk mengosongkan resource, sebaiknya hapus pengontrol animasi saat tidak diperlukan lagi.

b2f84ff91b0e1396.pngTambahkan metode dispose() ke _ChatScreenState.

Tambahkan metode berikut ke bagian bawah _ChatScreenState:

@override
void dispose() {
  for (var message in _messages){
    message.animationController.dispose();
  }
  super.dispose();
}

b2f84ff91b0e1396.pngKode sudah benar, tetapi tidak diformat dengan baik. Klik kanan di panel kode, lalu pilih Reformat Code with dartfmt.

b2f84ff91b0e1396.pngLakukan hot reload pada aplikasi (atau hot restart, jika aplikasi yang berjalan berisi pesan chat), dan masukkan beberapa pesan untuk mengamati efek animasinya.

Jika Anda ingin bereksperimen lebih lanjut dengan animasi, berikut beberapa ide untuk dicoba:

  • Percepat atau perlambat animasi dengan memodifikasi nilai duration yang ditentukan dalam metode _handleSubmitted().
  • Tentukan kurva animasi yang berbeda dengan menggunakan konstanta yang ditentukan di class Curves.
  • Buat efek animasi makin jelas dengan menggabungkan Container di widget FadeTransition, bukan SizeTransition.

Terjadi masalah?

Jika aplikasi tidak berjalan dengan baik, cari apakah ada salah ketik. Jika perlu, gunakan kode di link berikut untuk membuat aplikasi normal kembali.

Di langkah tambahan ini, beri aplikasi Anda beberapa detail canggih, seperti membuat tombol Kirim hanya aktif ketika ada teks yang akan dikirim, menggabung pesan yang lebih panjang, dan menambahkan penyesuaian bergaya native untuk Android dan iOS.

Membuat tombol kirim kontekstual

Saat ini, tombol Kirim muncul diaktifkan, meskipun ketika tidak ada teks di kolom input. Sebaiknya ubah tampilan tombol berdasarkan apakah kolom berisi teks yang akan dikirimkan.

b2f84ff91b0e1396.pngTentukan _isComposing, variabel pribadi yang nilainya benar kapan pun pengguna mengetik di kolom input:

class _ChatScreenState extends State<ChatScreen> with TickerProviderStateMixin {
  final List<ChatMessage> _messages = [];
  final _textController = TextEditingController();
  final FocusNode _focusNode = FocusNode();
  bool _isComposing = false;            // NEW

b2f84ff91b0e1396.pngTambahkan metode callback onChanged() ke _ChatScreenState.

Di metode _buildTextComposer(), tambahkan properti onChanged ke TextField, dan update properti onSubmitted:

Flexible(
  child: TextField(
    controller: _textController,
    onChanged: (String text) {            // NEW
      setState(() {                       // NEW
        _isComposing = text.isNotEmpty;   // NEW
      });                                 // NEW
    },                                    // NEW
    onSubmitted: _isComposing ? _handleSubmitted : null, // MODIFIED
    decoration:
        InputDecoration.collapsed(hintText: 'Send a message'),
    focusNode: _focusNode,
  ),
),

b2f84ff91b0e1396.pngUpdate metode callback onPressed() di _ChatScreenState.

Selagi masih di metode _buildTextComposer(), update properti onPressed untuk IconButton:

Container(
  margin: EdgeInsets.symmetric(horizontal: 4.0),
  child: IconButton(
      icon: const Icon(Icons.send),
      onPressed: _isComposing                            // MODIFIED
          ? () => _handleSubmitted(_textController.text) // MODIFIED
          : null,                                        // MODIFIED
      )
      ...
)

b2f84ff91b0e1396.pngUbah _handleSubmitted untuk menetapkan _isComposing ke salah ketika kolom teks kosong:

void _handleSubmitted(String text) {
  _textController.clear();
  setState(() {                             // NEW
    _isComposing = false;                   // NEW
  });                                       // NEW

  ChatMessage message = ChatMessage(
  ...

cf1e10b838bf60ee.png Pengamatan

  • Callback onChanged memberi tahu TextField bahwa pengguna mengedit teksnya. TextField memanggil metode ini setiap kali nilainya berubah dari nilai kolom saat ini.
  • Callback onChanged memanggil setState() untuk mengubah nilai _isComposing ke benar saat kolom berisi beberapa teks.
  • Jika _isComposing salah, properti onPressed ditetapkan ke null.
  • Properti onSubmitted juga diubah sehingga tidak akan menambahkan string kosong ke daftar pesan.
  • Variabel _isComposing kini mengontrol perilaku dan tampilan visual tombol Kirim.
  • Jika pengguna mengetik string di kolom teks, _isComposing adalah true, dan warna tombol ditetapkan ke Theme.of(context).accentColor. Saat pengguna menekan tombol Kirim, framework akan memanggil _handleSubmitted().
  • Jika pengguna tidak mengetik apa pun di kolom teks, _isComposing adalah false, dan properti onPressed widget ditetapkan ke null, menonaktifkan tombol Kirim. Framework ini otomatis mengubah warna tombol menjadi Theme.of(context).disabledColor.

b2f84ff91b0e1396.pngLakukan hot reload pada aplikasi Anda untuk mencobanya.

Menggabungkan baris yang panjang

Ketika pengguna mengirimkan pesan chat yang melebihi lebar UI untuk menampilkan pesan, baris harus digabung sehingga seluruh pesan ditampilkan. Saat ini, baris yang meluap dipotong, dan error luapan visual ditampilkan. Cara mudah untuk memastikan bahwa teks digabungkan benar adalah dengan memasukkannya ke dalam widget Expanded.

b2f84ff91b0e1396.pngGabungkan widget Column dengan widget Expanded:

  1. Di metode build() untuk ChatMessage, pilih widget Column di dalam Row untuk Container.
  2. Tekan Option+Return (macOS) atau Alt+Enter (Linux dan Windows) untuk memunculkan menu.
  3. Mulai ketik Expanded, dan pilih Expanded dari daftar objek yang mungkin.

Contoh kode berikut menunjukkan tampilan class ChatMessage setelah melakukan perubahan ini:

...
Container(
  margin: const EdgeInsets.only(right: 16.0),
  child: CircleAvatar(child: Text(_name[0])),
),
Expanded(            // NEW
  child: Column(     // MODIFIED
    crossAxisAlignment: CrossAxisAlignment.start,
    children: [
      Text(_name, style: Theme.of(context).textTheme.headline4),
      Container(
        margin: EdgeInsets.only(top: 5.0),
        child: Text(text),
      ),
    ],
  ),
),                    // NEW
...

cf1e10b838bf60ee.png Pengamatan

Widget Expanded memungkinkan widget turunannya (seperti Column) menerapkan batasan tata letak (dalam hal ini, lebar Column) pada widget turunan. Di sini, hal ini membatasi lebar widget Text, yang biasanya ditentukan oleh kontennya.

Menyesuaikan untuk Android dan iOS

Untuk memberikan tampilan dan nuansa yang natural kepada UI aplikasi Anda, Anda dapat menambahkan tema dan beberapa logika sederhana ke metode build() untuk class FriendlyChatApp. Di langkah ini, Anda menentukan tema platform yang menerapkan kumpulan warna akses dan primer yang berbeda. Anda juga menyesuaikan tombol Kirim untuk menggunakan Desain Material IconButton di Android dan CupertinoButton di iOS.

b2f84ff91b0e1396.pngTambahkan kode berikut ke main.dart, setelah metode main():

final ThemeData kIOSTheme = ThemeData(
  primarySwatch: Colors.orange,
  primaryColor: Colors.grey[100],
  primaryColorBrightness: Brightness.light,
);

final ThemeData kDefaultTheme = ThemeData(
  primarySwatch: Colors.purple,
  accentColor: Colors.orangeAccent[400],
);

cf1e10b838bf60ee.png Pengamatan

  • Objek kDefaultTheme ThemeData menentukan warna untuk Android (ungu dengan aksen oranye).
  • Objek kIOSTheme ThemeData menentukan warna untuk iOS (abu-abu terang dengan aksen oranye).

b2f84ff91b0e1396.pngUbah class FriendlyChatApp untuk membuat tema menjadi bervariasi menggunakan properti theme widget MaterialApp aplikasi Anda:

  1. Impor paket dasar di bagian atas file:
import 'package:flutter/foundation.dart';  // NEW
import 'package:flutter/material.dart';
  1. Ubah class FriendlyChatApp untuk memilih tema yang sesuai:
class FriendlyChatApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'FriendlyChat',
      theme: defaultTargetPlatform == TargetPlatform.iOS // NEW
        ? kIOSTheme                                      // NEW
        : kDefaultTheme,                                 // NEW
      home: ChatScreen(),
    );
  }
}

b2f84ff91b0e1396.pngUbah tema widget AppBar (banner di bagian atas UI aplikasi Anda).

  1. Di metode build() _ChatScreenState, temukan baris kode berikut:
      appBar: AppBar(title: Text('FriendlyChat')),
  1. Letakkan kursor di antara dua tanda kurung kanan ())), ketik koma, dan tekan Return untuk memulai baris baru.
  2. Tambahkan dua baris berikut:
elevation:
   Theme.of(context).platform == TargetPlatform.iOS ? 0.0 : 4.0, // NEW
  1. Klik kanan di panel kode, lalu pilih Reformat Code with dartfmt.

cf1e10b838bf60ee.png Pengamatan

  • Properti defaultTargetPlatform tingkat teratas dan operator conditional digunakan untuk memilih tema.
  • Properti elevation menentukan koordinat z dari AppBar. Nilai koordinat z dari 4.0 memiliki bayangan yang ditentukan (Android), dan nilai 0.0 tidak memiliki bayangan (iOS). .

b2f84ff91b0e1396.pngSesuaikan ikon kirim untuk Android dan iOS.

  1. Tambahkan impor berikut ke bagian atas main.dart:
import 'package:flutter/cupertino.dart';   // NEW
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
  1. Pada metode _buildTextComposer() _ChatScreenState, modifikasi baris yang menetapkan IconButton sebagai turunan dari Container. Ubah penetapan agar bersifat bersyarat di platform. Untuk iOS, gunakan CupertinoButton; jika tidak, tetap gunakan IconButton:
Container(
   margin: EdgeInsets.symmetric(horizontal: 4.0),
   child: Theme.of(context).platform == TargetPlatform.iOS ? // MODIFIED
   CupertinoButton(                                          // NEW
     child: Text('Send'),                                    // NEW
     onPressed: _isComposing                                 // NEW
         ? () =>  _handleSubmitted(_textController.text)     // NEW
         : null,) :                                          // NEW
   IconButton(                                               // MODIFIED
       icon: const Icon(Icons.send),
       onPressed: _isComposing ?
           () =>  _handleSubmitted(_textController.text) : null,
       )
   ),

b2f84ff91b0e1396.pngGabungkan Column level atas di widget Container, dan beri batas berwarna abu-abu muda di tepi atasnya.

Batas ini membantu membedakan secara visual panel aplikasi dari isi aplikasi di iOS. Untuk menyembunyikan batas di Android, terapkan logika yang sama dengan yang digunakan untuk panel aplikasi pada contoh kode sebelumnya:

  1. Dalam metode build() di _ChatScreenState, pilih Column yang muncul setelah body:.
  2. Tekan Option+Return (macOS) atau Alt+Enter (Linux dan Windows) untuk memunculkan menu, dan pilih Wrap with Container.
  3. Setelah akhir Column, tetapi sebelum akhir Container, tambahkan kode (ditampilkan) yang menambahkan tombol yang sesuai secara bersyarat tergantung pada platform.
@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
      title: Text('FriendlyChat'),
      elevation:
          Theme.of(context).platform == TargetPlatform.iOS ? 0.0 : 4.0, // NEW
    ),
    body: Container(
        child: Column(
          children: [
            Flexible(
              child: ListView.builder(
                padding: EdgeInsets.all(8.0),
                reverse: true,
                itemBuilder: (_, int index) => _messages[index],
                itemCount: _messages.length,
              ),
            ),
            Divider(height: 1.0),
            Container(
              decoration: BoxDecoration(color: Theme.of(context).cardColor),
              child: _buildTextComposer(),
            ),
          ],
        ),
        decoration: Theme.of(context).platform == TargetPlatform.iOS // NEW
            ? BoxDecoration(                                 // NEW
                border: Border(                              // NEW
                  top: BorderSide(color: Colors.grey[200]!), // NEW
                ),                                           // NEW
              )                                              // NEW
            : null),                                         // MODIFIED
  );
}

b2f84ff91b0e1396.png Hot reload aplikasi. Anda akan melihat berbagai warna, bayangan, dan tombol ikon untuk Android dan iOS.

Pixel 3XL

iPhone 11

Terjadi masalah?

Jika aplikasi tidak berjalan dengan baik, cari apakah ada salah ketik. Jika perlu, gunakan kode di link berikut untuk membuat aplikasi normal kembali.

Selamat!

Anda sekarang tahu dasar-dasar mem-build aplikasi seluler lintas platform dengan framework Flutter.

Yang kita bahas

  • Cara mem-build aplikasi Flutter dari nol
  • Cara menggunakan beberapa pintasan yang disediakan di Android Studio dan IntelliJ
  • Cara menjalankan, hot reload, dan men-debug aplikasi Flutter pada emulator, simulator, dan perangkat
  • Cara menyesuaikan antarmuka pengguna dengan widget dan animasi
  • Cara menyesuaikan antarmuka pengguna untuk Android dan iOS

Apa selanjutnya

Cobalah codelab Flutter lainnya.

Terus pelajari Flutter:

Untuk informasi selengkapnya tentang pintasan keyboard:

Anda mungkin perlu mendownload kode contoh untuk melihat contoh sebagai referensi atau memulai codelab di bagian tertentu. Untuk mendapatkan salinan kode contoh untuk codelab, jalankan perintah ini dari terminal Anda:

 git clone https://github.com/flutter/codelabs

Contoh kode untuk codelab ini ada di folder friendly_chat. Setiap folder langkah bernomor sesuai dengan tampilan kode di akhir langkah bernomor codelab ini. Anda juga dapat melepaskan kode dari file lib/main.dart dari salah satu langkah ini ke dalam instance DartPad dan menjalankannya dari sana.