Flutter 3.10: Sử dụng MediaQuery 1 cách hiệu quả

3 phút để đọc

Khi phát triển ứng dụng trên Flutter, sẽ không tránh khỏi những trường hợp mà ta cần biết thông tin về màn hình của thiết bị như: kích thước, mật độ điểm ảnh, orientation, dark mode,…Và Flutter cung cấp class MediaQuery để hỗ trợ lập trình viên trong việc này.

 // kích thước màn hình
 MediaQuery.of(context).size
 // Mật độ điểm ảnh
 MediaQuery.of(context).devicePixelRatio
 // Orientation
 MediaQuery.of(context).orientation
 ...

Tuy nhiên MediaQuery có một hạn chế, khi widget gọi hàm .of(context).xxx, nghĩa là widget đang lắng nghe sự thay đổi các data tróng MediaQuery, và nó sẽ rebuild UI khi data bất kì của MediaQuery thay đổi. Bạn đã hình dung ra được vấn đề chưa? Nếu giả sử muốn một widget render UI dựa theo size, nhưng nếu platformBrightness, devicePixelRatio, hay bất kì data từ MediaQuery thay đổi thì widget của bạn cũng bị rebuild.

Đây là example tái hiện:

class MyHomePage extends StatelessWidget {
  const MyHomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return const Scaffold(
        body: Center(
            child: SizeText()));
  }
}

class SizeText extends StatelessWidget {
  const SizeText({
    super.key,
  });

  @override
  Widget build(BuildContext context) {
    // Notice here :)
    print('build SizeText');
    return Text(MediaQuery.of(context).size.height.toString());
  }
}

Như bạn thấy trong gif, mình thay đổi light mode/dark mode (platformBrightness) thì SizeText bị rebuild mặc dù nó chỉ sử dụng thuộc tính size thôi. Từ ví dụ này chúng ta thấy rằng MediaQuery đang gây ra việc rebuild UI ko cần thiết như thế nào, đặc biệt tệ hơn nếu đó chúng ta widget với widget tree đồ sộ và phức tạp.

Để giải quyết vấn đề này, MediaQuery ơ Flutter phiên bản 3.10 đã cho ra mắt những method mới:

// kích thước màn hình
 MediaQuery.sizeOf(context)
 // Mật độ điểm ảnh
 MediaQuery.devicePixelRatioOf(context)
 // Orientation
 MediaQuery.orientationOf(context)
 ...

Với những method này, khi widget lắng nghe 1 data cụ thể nào từ MediaQuery thì khi data đó update thì widget sẽ được rebuild, các data khác có update hay không thì widget mặc kệ. Điều này giảm thiểu được việc rebuild UI không cần thiết. Hãy quay trở lại example.

class MyHomePage extends StatelessWidget {
  const MyHomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return const Scaffold(
        body: Center(
            child: SizeText()));
  }
}

class SizeText extends StatelessWidget {
  const SizeText({
    super.key,
  });

  @override
  Widget build(BuildContext context) {
    // Notice here :)
    print('build SizeText');
    return Text(MediaQuery.sizeOf(context).height.toString());
  }
}

Okay! widget không bị rebuild sau khi sử dụng MediaQuyery.sizeOf(context) 😄. À nếu bạn replace method cũ sang method mới ở trong code thì nên hot restart thay vì hot load nhé, vì hot load thì cái đăng kí lắng nghe MediaQuery cũ của widget vẫn còn, và widget sẽ vẫn bị rebuild. Mình đã tốn gần 1-2 tiếng để nhận ra điều này 😢

Cheatsheet

Nguồn: Facebook/Learnfluttertogether

Bonus

Khi mình tìm hiểu về MediaQuery thì nhận ra là class này được implement bằng InheritedWidget - một chủ đề yêu thích của mình. Các thư viện state management phổ biến như Bloc, Provider đều base từ InheritedWidget. Chưa kể trong cheatsheet trên có đề cập InheritedModel - giúp các widget có thể lắng nghe một phần data cụ thể từ InheritedWidget. Chắc chắc mình sẽ có bài viết về 2 class này. Hãy chờ nhé 😄

Kết luận

Well, đó là tất cả những gì mình muốn truyền tải trong bài viết này. Việc sử dụng những method mới của MediaQuery trong các dự án Flutter của bạn có thẻ giúp việc render UI hiệu quả hơn, performance sẽ được cải thiện hơn, mang lại trải nghiệm người dụng mượt mà và nhanh nhạy hơn.

Happy coding !!!

Tham khảo