InheritedWidget di Flutter

Terjemahan artikel telah disiapkan untuk siswa kursus Pengembang Seluler Flutter .










Akar pohon widget Flutter bisa sangat dalam ...







Sangat dalam.



Sifat komponen widget Flutter memungkinkan desain aplikasi yang sangat elegan, modular, dan fleksibel. Namun, ini juga dapat menghasilkan banyak kode boilerplate untuk meneruskan konteks. Lihat apa yang terjadi jika kita ingin meneruskan accountId dan scopeId dari halaman ke widget dua tingkat di bawah ini:



class MyPage extends StatelessWidget {
  final int accountId;
  final int scopeId;
  
  MyPage(this.accountId, this.scopeId);
  
  Widget build(BuildContext context) {
    return new MyWidget(accountId, scopeId);
  }
}

class MyWidget extends StatelessWidget {
  final int accountId;
  final int scopeId;
  
  MyWidget(this.accountId, this.scopeId);
  
  Widget build(BuildContext context) {
    // -   
    new MyOtherWidget(accountId, scopeId);
    ...
  }
}

class MyOtherWidget extends StatelessWidget {
  final int accountId;
  final int scopeId;
  
  MyOtherWidget(this.accountId, this.scopeId);
  
  Widget build(BuildContext context) {
    //  
    ...


Jika tidak dicentang, pola ini dapat dengan mudah menyebar ke seluruh basis kode. Kami secara pribadi telah membuat parameter lebih dari 30 widget dengan cara ini. Hampir setengah dari waktu kerja, widget menerima parameter hanya untuk meneruskannya lebih jauh, seperti pada MyWidgetcontoh di atas.



Status MyWidget tidak bergantung pada parameter, namun dibangun kembali setiap kali parameter berubah!


Tentu saja, harus ada cara yang lebih baik ...



Memperkenalkan InheritedWidget .



Singkatnya, ini adalah jenis widget khusus yang mendefinisikan konteks di root subpohon. Ini dapat secara efisien memberikan konteks ini ke setiap widget di subpohon itu. Pola akses seharusnya terlihat familier bagi developer Flutter:



final myInheritedWidget = MyInheritedWidget.of(context);


Konteks ini hanyalah kelas Dart. Jadi, itu bisa berisi apa pun yang Anda inginkan untuk barang di sana. Banyak konteks Flutter yang umum digunakan, seperti Styleatau MediaQuery, tidak lebih dari InheritedWidgets yang aktif di level tersebut MaterialApp.



Jika kami melengkapi contoh di atas menggunakan InheritedWidget, inilah yang kami dapatkan:



class MyInheritedWidget extends InheritedWidget {
  final int accountId;
  final int scopeId;

  MyInheritedWidget(accountId, scopeId, child): super(child);
  
  @override
  bool updateShouldNotify(MyInheritedWidget old) =>
    accountId != old.accountId || scopeId != old.scopeId;
}

class MyPage extends StatelessWidget {
  final int accountId;
  final int scopeId;
  
  MyPage(this.accountId, this.scopeId);
  
  Widget build(BuildContext context) {
    return new MyInheritedWidget(
      accountId,
      scopeId,
      const MyWidget(),
     );
  }
}

class MyWidget extends StatelessWidget {

  const MyWidget();
  
  Widget build(BuildContext context) {
    // -   
    const MyOtherWidget();
    ...
  }
}

class MyOtherWidget extends StatelessWidget {

  const MyOtherWidget();
  
  Widget build(BuildContext context) {
    final myInheritedWidget = MyInheritedWidget.of(context);
    print(myInheritedWidget.scopeId);
    print(myInheritedWidget.accountId);
    ...


Penting untuk diperhatikan:



  • Konstruktor sekarang constyang membuat widget ini dapat disimpan dalam cache; yang meningkatkan produktivitas.
  • Ketika parameter diperbarui, yang baru dibuat MyInheritedWidget. Namun, tidak seperti contoh pertama, subpohon tidak dibangun kembali. Alih-alih, Flutter memelihara registri internal yang melacak widget yang telah mengakses ini InheritedWidget, dan hanya membangun kembali yang menggunakan konteks itu. Dalam contoh ini, itu MyOtherWidget.
  • Jika hierarki dibuat ulang untuk alasan selain perubahan parameter, seperti perubahan orientasi, kode Anda masih bisa membuat yang baru InheritedWidget. Namun, karena parameternya tetap sama, widget di subpohon tidak akan diberitahukan. Ini adalah tujuan dari fungsi yang updateShouldNotifydiimplementasikan oleh Anda InheritedWidget.


Terakhir, mari kita bahas tentang praktik yang baik.



InheritedWidget harus berukuran kecil



Membebani mereka dengan banyak konteks menyebabkan hilangnya keuntungan kedua dan ketiga yang disebutkan di atas, karena Flutter tidak dapat menentukan bagian mana dari konteks yang sedang diperbarui dan bagian mana yang sedang digunakan oleh widget. Sebagai gantinya:



class MyAppContext {
  int teamId;
  String teamName;
  
  int studentId;
  String studentName;
  
  int classId;
  ...
}


Lebih suka melakukan:



class TeamContext {
  int teamId;
  String teamName;
}

class StudentContext {
  int studentId;
  String studentName;
}
 
class ClassContext {
  int classId;
  ...
}


Gunakan const untuk membuat widget Anda



Tanpa const, tidak ada pembangunan kembali subtree secara selektif. Flutter membuat instance baru dari setiap widget di subtree dan memanggilnya build(), membuang-buang siklus yang berharga, terutama jika metode build Anda cukup berat.



Perhatikan ruang lingkup InheritedWidget-s Anda



InheritedWidget-y ditempatkan di root pohon widget. Ini, pada kenyataannya, menentukan ruang lingkup mereka. Di tim kami, kami menemukan bahwa kemampuan mendeklarasikan konteks di mana pun di pohon widget itu berlebihan. Kami memutuskan untuk membatasi widget kontekstual kami untuk hanya menerima Scaffold(atau turunannya) sebagai anak-anak. Dengan cara ini kami memastikan bahwa konteks yang paling terperinci dapat berada di tingkat halaman, dan kami mendapatkan dua cakupan:



  • Widget tingkat aplikasi seperti MediaQuery. Mereka tersedia untuk setiap widget pada halaman manapun dalam aplikasi Anda, karena mereka berada di akar pohon widget aplikasi Anda.
  • Widget tingkat halaman seperti MyInheritedWidgetcontoh di atas.


Anda harus memilih satu atau lainnya tergantung di mana konteksnya berlaku.



Widget tingkat halaman tidak dapat melintasi batas rute



Sepertinya sudah jelas. Namun, ini memiliki implikasi serius karena sebagian besar aplikasi memiliki lebih dari satu tingkat navigasi. Seperti inilah tampilan aplikasi Anda:



> School App [App Context]
  > Student [Student Context]
    > Grades
    > Bio
  > Teacher [Teacher Context]
    > Courses
    > Bio


Inilah yang dilihat Flutter:



> School App [App Context]
  > Student [Student Context]
  > Student Grades
  > Student Bio
  > Teacher [Teacher Context]
  > Teacher Courses
  > Teacher Bio


Dari perspektif Flutter, tidak ada hierarki navigasi. Setiap halaman (atau scaffold) adalah pohon widget yang terkait dengan widget aplikasi. Oleh karena itu, saat Anda menggunakan Navigator.pushhalaman ini untuk menampilkan, mereka tidak mewarisi widget yang membawa konteks induk. Dalam contoh di atas, Anda harus secara eksplisit meneruskan konteks Studentdari halaman Siswa ke halaman Biografi Siswa.



Meskipun ada berbagai cara untuk meneruskan konteks, saya menyarankan parameterisasi rute dengan cara lama (misalnya, pengkodean URL jika Anda menggunakan rute bernama). Ini juga memastikan bahwa halaman dapat dibangun murni berdasarkan rute tanpa harus menggunakan konteks halaman induknya.



Selamat membuat kode!



Tepat waktu untuk kursus!



All Articles