Flutter - Other

Using Future.wait to wait for the results of multiple Futures to complete

class someAPI {
  Future<int> getThings() => Future.value(3000);
  Future<int> getItems() => Future.value(300);
  Future<int> getStuff() => Future.value(30);
}

final api = someAPI();
final values = await Future.wait(
  [
    api.getThings(),
    api.getItems(),
    api.getStuff()
  ]
);

Using GridView.count to create a grid that's two tiles wide in portrait mode and three tiles wide in landscape mode

Flexible(
  child: GridView.count(
    crossAxisCount: (orientation == Orientaation.portrait) ? 2 : 3,
    mainAxisSpacing: 4.0,
    crossAxisSpacing: 4.0,
    padding: const EdgeInsets.all(4.0),
    childAspectRatio: (orientation == Orientaation.portrait) ? 1.0 : 1.3,
    children: someList.map(
      (catData) => aListItemWidget(catData)
    ).toList()
  )
);

Using services library to lock the device orientation

import 'package:flutter/servics.dart';

...

void main() async {
  await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);

  runApp(App());
}

Using Sized Box to constraints the size

Widget to the list of children, you will need to change the height of the Sizedbox whenever you add a widget.


Don't

SizedBox(
  height: 20,
  child: Column(
    children: [
      Text(Hey),
      Text(You)
    ]
  ) 
)


Do

Column(
  mainAxisSize: MainAxisSize.min,
  child: Column(
    children: [
      Text(Hey),
      Text(You)
    ]
  ) 
)

Want to log data on the system console in Flutter

You can use the print0 function to view it in the system console. If your output is too much, then Android sometimes discards some log lines. To avoid this, you can use debugPrint().


You can also log your print calls to disk if you're doing long-term or background work.

Want to set different Theme for a particular widget

Just wrap the widget with the Theme Widget and pass the ThemeData().


Theme(
  data: ThemeData(...),
  child: TextFormField(
    decoration: const InputDecoration(
      icon: Icon(Icons.person),
      hintText: 'What do people call you ?',
      labelText: 'Name *',
    )
    validator: (value) {
      return value!.contains('@') ? 'Do not use the @ char' : null;
    }
  )
)

warning: [options] source value 7 is obsolete and will be removed in a future release

  1. You can set your JAVA_HOME to an older version of Java and then run your Flutter build. You would also need to have this JAVA_HOME available anywhere you build from (command line, IDE, CI/CD, etc)
  2. Update your allprojects section of android/build.gradle to look like the following:
allprojects {
  repositories {
    google()
    mavenCentral()
  }

  tasks.withType(JavaCompile).configureEach {
    javaCompiler = javaToolchains.compilerFor {
      languageVersion = JavaLanguageVersion.of(8)
    }
  }
}

WebView Content Blockers

Add triggers to your Content Blocker

initialSettings: InAppWebViewSettings(contentBlockers: [
  ContentBlocker(
    trigger: ContentBlockerTrigger(
      urlFilter: ".*",
      resourceType: [
        ContentBlockerTriggerResourceType.IMAGE,
        ContentBlockerTriggerResourceType.STYLE_SHEET
      ],
      unlessDomain: ["example.com", "github.com", "pub.dev"]
    ),
    action: ContentBlockerAction(
      type: ContentBlockerActionType.BLOCK
    )
  )
]),


For deeper trigger customization, you can use the other properties of ContentBlockerTrigger :

  • urlFilterIsCaseSensitive: If the URL matching should be case-sensitive. By default, it is case insensitive.
  • resourceType: A list of “ContentBlockerTriggerResourceType” representing the resource types (how the browser intends to use the resource) that the rule should match. If not specified, the rule matches all resource types.
  • ifDomain: A list of strings matched to a URL’s domain; limits action to a list of specific domains. Values must be lowercase ASCII, or punycode for non-ASCII. Add * in front to match domain and subdomains. It can’t be used with “unlessDomain”.
  • unlessDomain: A list of strings matched to a URL’s domain; acts on any site except domains in a provided list. Values must be lowercase ASCII, or punycode for non-ASCII. Add * in front to match domain and subdomains. It can’t be used with “ifDomain”.
  • loadType: A list of “ContentBlockerTriggerLoadType” that can include one of two mutually exclusive values. If not specified, the rule matches all load types. “ContentBlockerTriggerLoadType.FIRST_PARTY” triggers only if the resource has the same scheme, domain, and port as the main page resource. “ContentBlockerTriggerLoadType.THIRD_PARTY” triggers if the resource isn’t from the same domain as the main page resource.
  • ifTopUrl: A list of strings matched to the entire main document URL; limits the action to a specific list of URL patterns. Values must be lowercase ASCII, or punycode for non-ASCII. It can’t be used with “unlessTopUrl”.
  • unlessTopUrl: An array of strings matched to the entire main document URL; acts on any site except URL patterns in the provided list. Values must be lowercase ASCII, or punycode for non-ASCII. It can’t be used with “ifTopUrl”.
  • loadContext: An array of strings that specify loading contexts.
  • ifFrameUrl: A list of regular expressions to match iframes URL against.


Add actions to your Content Blocker

initialSettings: InAppWebViewSettings(contentBlockers: [
  ContentBlocker(
    trigger: ContentBlockerTrigger(
      urlFilter: "https://flutter.dev/.*",
    ),
    action: ContentBlockerAction(
      type: ContentBlockerActionType.CSS_DISPLAY_NONE,
      selector: '.notification, .media, #developer-story'
    )
  )
]),


Valid types are:

  • BLOCK: Stops loading of the resource. If the resource was cached, the cache is ignored.
  • BLOCK_COOKIES: Strips cookies from the header before sending it to the server. This only blocks cookies otherwise acceptable to WebView’s privacy policy. Combining with “IGNORE_PREVIOUS_RULES” doesn’t override the browser’s privacy settings.
  • CSS_DISPLAY_NONE: Hides elements of the page based on a CSS selector. A selector field contains the selector list. Any matching element has its display property set to none, which hides it.
  • MAKE_HTTPS: Changes a URL from http to https. URLs with a specified (nondefault) port and links using other protocols are unaffected.
  • IGNORE_PREVIOUS_RULES: Ignores previously triggered actions.


Creating a simple Ad Blocker

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';

Future main() async {
  WidgetsFlutterBinding.ensureInitialized();
  if (!kIsWeb &&
      kDebugMode &&
      defaultTargetPlatform == TargetPlatform.android) {
    await InAppWebViewController.setWebContentsDebuggingEnabled(kDebugMode);
  }
  runApp(const MaterialApp(home: MyApp()));
}

class MyApp extends StatefulWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  final GlobalKey webViewKey = GlobalKey();

  // list of Ad URL filters to be used to block ads loading.
  final adUrlFilters = [
    ".*.doubleclick.net/.*",
    ".*.ads.pubmatic.com/.*",
    ".*.googlesyndication.com/.*",
    ".*.google-analytics.com/.*",
    ".*.adservice.google.*/.*",
    ".*.adbrite.com/.*",
    ".*.exponential.com/.*",
    ".*.quantserve.com/.*",
    ".*.scorecardresearch.com/.*",
    ".*.zedo.com/.*",
    ".*.adsafeprotected.com/.*",
    ".*.teads.tv/.*",
    ".*.outbrain.com/.*"
  ];

  final List<ContentBlocker> contentBlockers = [];
  var contentBlockerEnabled = true;

  InAppWebViewController? webViewController;

  @override
  void initState() {
    super.initState();

    // for each Ad URL filter, add a Content Blocker to block its loading.
    for (final adUrlFilter in adUrlFilters) {
      contentBlockers.add(ContentBlocker(
          trigger: ContentBlockerTrigger(
            urlFilter: adUrlFilter,
          ),
          action: ContentBlockerAction(
            type: ContentBlockerActionType.BLOCK,
          )));
    }

    // apply the "display: none" style to some HTML elements
    contentBlockers.add(ContentBlocker(
        trigger: ContentBlockerTrigger(
          urlFilter: ".*",
        ),
        action: ContentBlockerAction(
            type: ContentBlockerActionType.CSS_DISPLAY_NONE,
            selector: ".banner, .banners, .ads, .ad, .advert")));
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: const Text("Ads Content Blocker"),
          actions: [
            TextButton(
              onPressed: () async {
                contentBlockerEnabled = !contentBlockerEnabled;
                if (contentBlockerEnabled) {
                  await webViewController?.setSettings(
                      settings: InAppWebViewSettings(
                          contentBlockers: contentBlockers));
                } else {
                  await webViewController?.setSettings(
                      settings: InAppWebViewSettings(contentBlockers: []));
                }
                webViewController?.reload();

                setState(() {});
              },
              style: TextButton.styleFrom(foregroundColor: Colors.white),
              child: Text(contentBlockerEnabled ? 'Disable' : 'Enable'),
            )
          ],
        ),
        body: SafeArea(
            child: Column(children: <Widget>[
          Expanded(
            child: Stack(
              children: [
                InAppWebView(
                  key: webViewKey,
                  initialUrlRequest:
                      URLRequest(url: WebUri('https://www.tomshardware.com/')),
                  initialSettings:
                      InAppWebViewSettings(contentBlockers: contentBlockers),
                  onWebViewCreated: (controller) {
                    webViewController = controller;
                  },
                ),
              ],
            ),
          ),
        ])));
  }
}

Widgets instead of functions

Do

@override
Widget build(BuildContext context) {
  return Scaffold(
    body: Column(
      children: const [
        Widget(),
        Widget(),
        Widget()
      ]
    )
  );
}


Don't

@override
Widget build(BuildContext context) {
  return Scaffold(
    body: Column(
      children: [
        WidgetFunction(),
        WidgetFunction(),
        WidgetFunction()
      ]
    )
  );
}


Conclusion: Extracting widgets to a method is considered as a Flutter anti-pattern, because when Flutter rebuilds widget tree, it calls the function all the time, making more processor time for the operations while widgets they will be rendered once and will not update themselves.


WidgetsBinding Mixin Unleashed

The Widget Lifecycle 🛞

Understanding the widget lifecycle is essential for effective Flutter development. The WidgetsBinding mixin facilitates the management of this lifecycle through various methods. Let's explore some key methods that the mixin introduces:


initState()

The initState() method is called when a StatefulWidget is inserted into the widget tree for the first time. It provides an opportunity to initialize the state of the widget, making it a suitable place for tasks like data fetching or setting up event listeners.

@override
void initState() {
  super.initState();
  // Initialize state here
}


build()

The build() method is where the UI of the widget is constructed. It is invoked whenever the widget is rebuilt, allowing developers to create a dynamic and responsive user interface.

@override
Widget build(BuildContext context) {
  return // Your UI components here
}


dispose()

The dispose() method is called when the widget is removed from the widget tree. Developers can use this method to perform cleanup tasks such as releasing resources or canceling subscriptions.

@override
void dispose() {
  // Clean up resources here
  super.dispose();
}


didChangeAppLifecycleState()

The didChangeAppLifecycleState method is invoked whenever the application's lifecycle state changes. This includes transitioning between the foreground and background, providing an opportunity to pause or resume tasks as needed.

@override
void didChangeAppLifecycleState(AppLifecycleState state) {
  if (state == AppLifecycleState.resumed) {
    // App resumed, reactivate tasks
  } else if (state == AppLifecycleState.paused) {
    // App paused, deactivate tasks
  }
}


didChangePlatformBrightness()

This method is called when the platform brightness changes, allowing developers to adapt their UI based on changes in ambient light conditions.

@override
void didChangePlatformBrightness() {
  // Adjust UI based on brightness
}


didUpdateWidget()

The didUpdateWidget method is invoked whenever the framework replaces an old widget with a new one. This occurs when the parent widget rebuilds and creates a new instance of the widget. This method is beneficial for responding to changes in widget properties.

@override
void didUpdateWidget(MyWidget oldWidget) {
  // React to changes in widget properties
  super.didUpdateWidget(oldWidget);
}


reassemble()

The reassemble method is called during hot-reloading, providing developers with an opportunity to update the UI or reset state without restarting the application.

@override
void reassemble() {
  // Perform actions for hot-reloading
  super.reassemble();
}


Frame Scheduling and Animation 📅

Flutter’s smooth animations and delightful user interfaces are often attributed to its advanced rendering pipeline. The WidgetsBinding mixin provides methods that play a pivotal role in orchestrating frame scheduling and handling animations.


scheduleFrameCallback

The scheduleFrameCallback method allows you to register a callback that will be invoked before the next frame is rendered. This is particularly useful for orchestrating complex animations and interactions.

void myAnimationCallback(Duration timeStamp) {
  // Update animation state here
}

void scheduleAnimation() {
  WidgetsBinding.instance?.scheduleFrameCallback(myAnimationCallback);
}


addPostFrameCallback

The addPostFrameCallback method schedules a callback to be invoked after the frame has been rendered. This is beneficial when you need to perform actions after the UI has settled.

void myPostFrameCallback(Duration timeStamp) {
  // Perform actions after the frame has been rendered
}

void schedulePostFrameCallback() {
  WidgetsBinding.instance?.addPostFrameCallback(myPostFrameCallback);
}


User Interaction and System Events 🎊

Beyond managing the widget lifecycle and animations, the WidgetsBinding mixin provides hooks for handling user interaction and system events.


handlePointerEvent

The handlePointerEvent method allows you to intercept and handle low-level pointer events. This can be useful for implementing custom gestures or interactions.

void handlePointerEvent(PointerEvent event) {
  // Handle pointer events
}

WidgetsBinding.instance?.handlePointerEvent = handlePointerEvent;


handlePopRoute

The handlePopRoute method is called when the system requests to pop the current route. This can be useful for handling back button presses.

@override
Future<bool> handlePopRoute() async {
  // Handle the pop route request
  return true; // Return true if the route is popped
}