Flutter - Other

.pub-cache

What is .pub-cache?

In simple terms, .pub-cache is a hidden folder on your computer where Flutter (and Dart) stores all the packages and dependencies you've added to your project. When you use flutter pub get to fetch packages from pub.dev (the Dart and Flutter package repository), the downloaded packages are cached in this folder. It acts like a local storage locker, keeping everything you need for your app to run smoothly.


The location of the .pub-cache folder is typically:

  • macOS and Linux: ~/.pub-cache
  • Windows: C:\Users\<your_user>\AppData\Roaming\Pub\Cache


It's like the Flutter library's personal library of all the packages you've ever used!


Clear the Cache 🚿

If things get a little too messy, clearing out the .pub-cache folder can help. Run the following command to delete the cache:


flutter pub cache repair

This will remove the cache and re-download all your packages, which is helpful when you're dealing with strange package errors.


Check Cache Location 🔍

If you're curious about where exactly Flutter is storing the .pub-cache folder on your machine, you can use this command to check:


flutter pub cache
It'll show you the exact path, and you'll have a much better understanding of where all your cached packages are hanging out.

.withOpacity deprecated in Flutter 3.27.0, and what is its recommended replacement?

// Before
final x = color.withOpacity(0.0);

// After
final x = color.withValues(alpha: 0.0);

A RenderFlex overflowed

Well, you need to make sure the Column won't attempt to be wider than it can be. To achieve this, you need to constrain its width. One way to do it is to wrap the Column in an Expanded widget.


child: Row(
  children : [
    Icon (Icons.message),
    Expanded (
      child: Column(
        code omitted
      ),
    ),
  ]
)

AdMob Error Codes & Logs

Error Code 0 : ERROR_CODE_INTERNAL_ERROR

Something happened internally; for instance, an invalid response was received from the ad server.


Error Code 1 : ERROR_CODE_INVALID_REQUEST

The ad request was invalid; for instance, the ad unit ID was incorrect.


Error Code 2 : ERROR_CODE_NETWORK_ERROR

The ad request was unsuccessful due to network connectivity.


Error Code 3 : ERROR_CODE_NO_FILL

The ad request was successful, but no ad was returned due to lack of ad inventory.

Android OS Build Version

Current Development Version

  • Build.VERSION_CODES - CUR_DEVELOPMENT
  • SDK_INT Value - 10000


Android 13

  • Build.VERSION_CODES - T
  • Platform Version - Android 13.0
  • API Level - 33
  • SDK_INT Value - 33


Android 12L

  • Build.VERSION_CODES - S_V2
  • Platform Version - Android 12.1
  • API Level - 32
  • SDK_INT Value - 32


Android 12

  • Build.VERSION_CODES - S
  • Platform Version - Android 12.0
  • API Level - 31
  • SDK_INT Value - 31


Android 11

  • Build.VERSION_CODES - R
  • Platform Version - Android 11.0
  • API Level - 30
  • SDK_INT Value - 30


Android 10

  • Build.VERSION_CODES - Q
  • Platform Version - Android 10.0
  • API Level - 29
  • SDK_INT Value - 29


Android 9 Pie

  • Build.VERSION_CODES - P
  • Platform Version - Android 9.0
  • API Level - 28
  • SDK_INT Value - 28


Android 8 Oreo MR1

  • Build.VERSION_CODES - O_MR1
  • Platform Version - Android 8.1
  • API Level - 27
  • SDK_INT Value - 27


Android 8.0 Oreo

  • Build.VERSION_CODES - O
  • Platform Version - Android 8.0
  • API Level - 26
  • SDK_INT Value - 26


Android 7.1.1 Nougat

  • Build.VERSION_CODES - N_MR1
  • Platform Version - Android 7.1
  • API Level - 25
  • SDK_INT Value - 25


Android 7.0 Nougat

  • Build.VERSION_CODES - N
  • Platform Version - Android 7.0
  • API Level - 24
  • SDK_INT Value - 24


Android 6.0 Marshmallow

  • Build.VERSION_CODES - M
  • Platform Version - Android 6.0
  • API Level - 23
  • SDK_INT Value - 23


Android 5.1 Lollipop

  • Build.VERSION_CODES - LOLLIPOP_MR1
  • Platform Version - Android 5.1
  • API Level - 22
  • SDK_INT Value - 22


Android 5.0 Lollipop

  • Build.VERSION_CODES - LOLLIPOP
  • Platform Version - Android 5.0
  • API Level - 21
  • SDK_INT Value - 21


Android 4.4 KitKat Watch

  • Build.VERSION_CODES - KITKAT_WATCH
  • Platform Version - Android 4.4W
  • API Level - 20
  • SDK_INT Value - 20


Android 4.4 KitKat

  • Build.VERSION_CODES - KITKAT
  • Platform Version - Android 4.4
  • API Level - 19
  • SDK_INT Value - 19


Android 4.3 Jellybean

  • Build.VERSION_CODES - JELLY_BEAN_MR2
  • Platform Version - Android 4.3
  • API Level - 18
  • SDK_INT Value - 18


Android 4.2 Jellybean

  • Build.VERSION_CODES - JELLY_BEAN_MR1
  • Platform Version - Android 4.2
  • API Level - 17
  • SDK_INT Value - 17


Android 4.1 Jellybean

  • Build.VERSION_CODES - JELLY_BEAN
  • Platform Version - Android 4.1
  • API Level - 16
  • SDK_INT Value - 16


Android 4.0.3 Ice Cream Sandwich

  • Build.VERSION_CODES - ICE_CREAM_SANDWICH_MR1
  • Platform Version - Android 4.0.3
  • API Level - 15
  • SDK_INT Value - 15


Android 4.0 Ice Cream Sandwich

  • Build.VERSION_CODES - ICE_CREAM_SANDWICH
  • Platform Version - Android 4.0
  • API Level - 14
  • SDK_INT Value - 14


Android 3.2 Honeycomb

  • Build.VERSION_CODES - HONEYCOMB_MR2
  • Platform Version - Android 3.2
  • API Level - 13
  • SDK_INT Value - 13


Android 3.1 Honeycomb

  • Build.VERSION_CODES - HONEYCOMB_MR1
  • Platform Version - Android 3.1
  • API Level - 12
  • SDK_INT Value - 12


Android 3.0 Honeycomb

  • Build.VERSION_CODES - HONEYCOMB
  • Platform Version - Android 3.0
  • API Level - 11
  • SDK_INT Value - 11


Android 2.3.3 Gingerbread

  • Build.VERSION_CODES - GINGERBREAD_MR1
  • Platform Version - Android 2.3.3
  • API Level - 10
  • SDK_INT Value - 10


Android 2.3 Gingerbread

  • Build.VERSION_CODES - GINGERBREAD
  • Platform Version - Android 2.3
  • API Level - 9
  • SDK_INT Value - 9


Android 2.2 Froyo

  • Build.VERSION_CODES - FROYO
  • Platform Version - Android 2.2
  • API Level - 8
  • SDK_INT Value - 8


Android 2.1 Eclair

  • Build.VERSION_CODES - ECLAIR_MR1
  • Platform Version - Android 2.1
  • API Level - 7
  • SDK_INT Value - 7


Android 2.0.1 Eclair

  • Build.VERSION_CODES - ECLAIR_0_1
  • Platform Version - Android 2.0.1
  • API Level - 6
  • SDK_INT Value - 6


Android 2.0 Eclair

  • Build.VERSION_CODES - ECLAIR
  • Platform Version - Android 2.0
  • API Level - 5
  • SDK_INT Value - 5


Android 1.6 Donut

  • Build.VERSION_CODES - DONUT
  • Platform Version - Android 1.6
  • API Level - 4
  • SDK_INT Value - 4


Android 1.5 Cupcake

  • Build.VERSION_CODES - CUPCAKE
  • Platform Version - Android 1.5
  • API Level - 3
  • SDK_INT Value - 3


Android 1.1 Petit Four

  • Build.VERSION_CODES - BASE_1_1
  • Platform Version - Android 1.1
  • API Level - 2
  • SDK_INT Value - 2


Android 1.0 (no codename)

  • Build.VERSION_CODES - BASE
  • Platform Version - Android 1.0
  • API Level - 1
  • SDK_INT Value - 1

Async/await instead of then

Do

void getActiveUserCount() async {
  try {
    var users = await getActiveUsers();
    debugPrint(users.length.toString());
  }
  catch (error)
  {
    debugPrint(error.toString());
  }
}


Don't

void getActiveUserCount() {
  getActiveUsers().then((user) {
    debugPrint(users.length.toString());
  }).catchError((error) {
    debugPrint(error.toString());
  }
}


Conclusion: There's no meaningful difference between the two versions of your code. Both achieve the same result. But await can often save you a bunch of lines of code making your code simpler to read, test and maintain.

Automatically Signing Flutter Apps with a Keystore

Copy the Key File to the Android Directory:

Place the .jks keystore file in your android directory. For example:

android/key17.jks


Create a keystore.properties File:

Inside the android directory, create a new file named keystore.properties (this name is common, but you can use a different one if desired). In this file, add the following contents:

storePassword=123456
keyPassword=123456
keyAlias=key17
storeFile=../key17.jks
Note: Ensure that the keyAlias matches the alias used when creating the key, and that the passwords are correct.



Add Configuration in build.gradle:

In the android folder, open the build.gradle file and load the keystore.properties file before the android block:

def keystorePropertiesFile = rootProject.file("keystore.properties")
def keystoreProperties = new Properties()
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))


Set Up Signing Information in build.gradle:

Add the signing configuration inside the android block, referencing the values from the keystore.properties file:

android {
  signingConfigs {
    release {
      keyAlias keystoreProperties['keyAlias']
      keyPassword keystoreProperties['keyPassword']
      storeFile file(keystoreProperties['storeFile'])
      storePassword keystoreProperties['storePassword']
    }
  }
  buildTypes {
    release {
      signingConfig signingConfigs.release
    }
  }
  ...
}


Rebuild Your App:

Once the configuration is complete, rebuild your app using:

flutter build apk --release

Avoid StatefulWidgets as much as possible

Do

class CustomTextButton extends StatelessWidget {
  ...
}


Don't

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

  @override
  State<CustomTextButton> createState() => _CustomTextButtonState();
}

class _CustomTextButtonState extends State<CustomTextButton>{
  ...
}


Conclusion: StatefulWidget can rebuild itself, it also decreases the performance. That's why always use StatelessWidget and only if necessary StatefulWidget and use constructor of the reusable widget.

Avoid using as instead, use is operator

Usually, The as cast operator throws an exception if the cast is not possible. To avoid an exception being thrown, one can use is.


Do

if (item is Animal)
  item.name = 'Lion';


Don't

(item as Animal).name = 'Lion';

Avoid using leading underscore for local identifiers that aren’t private.

Dart uses a leading underscore(_) in an identifier to mark members and top-level declarations as private. There is no concept of. private using leading underscore(_) for local variables, parameters, local functions, or library prefixes.

Background Services

The flutter_background_services package allows you to manage background services in Flutter, providing a way to perform tasks even when the app is not in the foreground. Here’s a guide on how to use it for both Android and iOS:


Setup

Add Dependency:

Add flutter_background_services to your pubspec.yaml file:

dependencies:
  flutter:
    sdk: flutter
  flutter_background_services: ^1.0.0


Configure Android Manifest:

Add necessary permissions and service definitions in AndroidManifest.xml:

<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" package="com.example">
  ...
  <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
  <!--
    Permission to use here depends on the value you picked for foregroundServiceType - see the Android documentation.
    Eg, if you picked 'location', use 'android.permission.FOREGROUND_SERVICE_LOCATION'
  -->
  <uses-permission android:name="android.permission.FOREGROUND_SERVICE_..." />
  <application
        android:label="example"
        android:name="${applicationName}"
        android:icon="@mipmap/ic_launcher"
        ...>

        <activity
            android:name=".MainActivity"
            android:exported="true"
            ...>

        <!--Add this-->
        <service
            android:name="id.flutter.flutter_background_service.BackgroundService"
            android:foregroundServiceType="WhatForegroundServiceTypeDoYouWant"
        />
        <!--end-->

        ...
  ...
  </application>
</manifest>


Create Service Function:

Implement the background service in your Dart code. Define what the service should do in the background.

import 'package:flutter_background_services/flutter_background_services.dart';


void main() {
  WidgetsFlutterBinding.ensureInitialized();
  await initializeService();
  runApp(MyApp());
}


Future<void> initializeService() async {
  final service = FlutterBackgroundService();

  /// OPTIONAL, using custom notification channel id
  const AndroidNotificationChannel channel = AndroidNotificationChannel(
    'my_foreground', // id
    'MY FOREGROUND SERVICE', // title
    description:
        'This channel is used for important notifications.', // description
    importance: Importance.low, // importance must be at low or higher level
  );

  final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
      FlutterLocalNotificationsPlugin();

  if (Platform.isIOS || Platform.isAndroid) {
    await flutterLocalNotificationsPlugin.initialize(
      const InitializationSettings(
        iOS: DarwinInitializationSettings(),
        android: AndroidInitializationSettings('ic_bg_service_small'),
      ),
    );
  }

  await flutterLocalNotificationsPlugin
      .resolvePlatformSpecificImplementation<
          AndroidFlutterLocalNotificationsPlugin>()
      ?.createNotificationChannel(channel);

  await service.configure(
    androidConfiguration: AndroidConfiguration(
      // this will be executed when app is in foreground or background in separated isolate
      onStart: onStart,

      // auto start service
      autoStart: true,
      isForegroundMode: true,

      notificationChannelId: 'my_foreground',
      initialNotificationTitle: 'AWESOME SERVICE',
      initialNotificationContent: 'Initializing',
      foregroundServiceNotificationId: 888,
      foregroundServiceTypes: [AndroidForegroundType.location],
    ),
    iosConfiguration: IosConfiguration(
      // auto start service
      autoStart: true,
      // this will be executed when app is in foreground in separated isolate
      onForeground: onStart,
      // you have to enable background fetch capability on xcode project
      onBackground: onIosBackground,
    ),
  );
}

// to ensure this is executed
// run app from xcode, then from xcode menu, select Simulate Background Fetch

@pragma('vm:entry-point')
Future<bool> onIosBackground(ServiceInstance service) async {
  WidgetsFlutterBinding.ensureInitialized();
  DartPluginRegistrant.ensureInitialized();

  SharedPreferences preferences = await SharedPreferences.getInstance();
  await preferences.reload();
  final log = preferences.getStringList('log') ?? <String>[];
  log.add(DateTime.now().toIso8601String());
  await preferences.setStringList('log', log);

  return true;
}

@pragma('vm:entry-point')
void onStart(ServiceInstance service) async {
  // Only available for flutter 3.0.0 and later
  DartPluginRegistrant.ensureInitialized();

  // For flutter prior to version 3.0.0
  // We have to register the plugin manually

  SharedPreferences preferences = await SharedPreferences.getInstance();
  await preferences.setString("hello", "world");

  /// OPTIONAL when use custom notification
  final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
      FlutterLocalNotificationsPlugin();

  if (service is AndroidServiceInstance) {
    service.on('setAsForeground').listen((event) {
      service.setAsForegroundService();
    });

    service.on('setAsBackground').listen((event) {
      service.setAsBackgroundService();
    });
  }

  service.on('stopService').listen((event) {
    service.stopSelf();
  });

  // bring to foreground
  Timer.periodic(const Duration(seconds: 1), (timer) async {
    if (service is AndroidServiceInstance) {
      if (await service.isForegroundService()) {
        /// OPTIONAL for use custom notification
        /// the notification id must be equals with AndroidConfiguration when you call configure() method.
        flutterLocalNotificationsPlugin.show(
          888,
          'COOL SERVICE',
          'Awesome ${DateTime.now()}',
          const NotificationDetails(
            android: AndroidNotificationDetails(
              'my_foreground',
              'MY FOREGROUND SERVICE',
              icon: 'ic_bg_service_small',
              ongoing: true,
            ),
          ),
        );

        // if you don't using custom notification, uncomment this
        service.setForegroundNotificationInfo(
          title: "My App Service",
          content: "Updated at ${DateTime.now()}",
        );
      }
    }

    /// you can see this log in logcat
    debugPrint('FLUTTER BACKGROUND SERVICE: ${DateTime.now()}');

    // test using external plugin
    final deviceInfo = DeviceInfoPlugin();
    String? device;
    if (Platform.isAndroid) {
      final androidInfo = await deviceInfo.androidInfo;
      device = androidInfo.model;
    } else if (Platform.isIOS) {
      final iosInfo = await deviceInfo.iosInfo;
      device = iosInfo.model;
    }

    service.invoke(
      'update',
      {
        "current_date": DateTime.now().toIso8601String(),
        "device": device,
      },
    );
  });
}


Configure Info.plist:

Update Info.plist to include the required permissions and capabilities:

<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
    <string>dev.flutter.background.refresh</string>
</array>


Note that iOS imposes strict limitations on background tasks. Ensure you adhere to Apple’s guidelines for background execution to avoid your app being rejected during review.

Better Error Screen

void main() {
  ErrorWidget.builder = (FlutterErrorDetails details) {
    bool inDebug = false;

    assert(() {
      inDebug = true;
      return true;
    }

    if(inDebug) {
      return ErrorWidget(details.exception);
    }
    return Material(
      color: Colors.green.shade200,
      child: Center(
        child: Text(details.exception.toString()),
        style: const TextStyle(
          color: Colors.white,
          fontWeight: FontWeight.bold,
          fontSize: 20
        )
      )
    );
  };

  runApp(const MyApp());
}