App download size plays a key role in determining whether users choose to install it. If you have already converted your Images like PNG, JPG to Vectors (SVGs) then you are already off to a great start. However when a project scales, optimising asset sizes alone is not enough. There are various libraries that get platform dependent, which might get shipped for other platforms too.
In this article, we are going to solve this by removing OS specific Dart code using CodeMod.
Identifying OS specific Dart Code
All the code blocks that gets branched with if conditions like below are OS specific and not required to get shipped to other platforms.
@Override
Widget build(BuildContext context) {
if (Platform.isIOS) {
return CupertinoButton(...);
} else {
return ElevatedButton(...);
}
}
That is, when compiling project for iOS, the else code block is not required at all and similarly for compiling project for Android, the if block is not required. After such needless code blocks are removed, the imports become unused. Such unused import statements are removed by Dart Tree shaking mechanism when compiling the project. Let’s get started!
I’m one among the engineers building Zoho Tables mobile apps using Flutter, Try it out on App Store and Play Store.
Zoho Tables is a productivity tool to organize anything from personal tasks to managing Team Work! You can create your custom database using 20+ field types (including Keyword extraction, Sentiment Analysis, Language detection), or Use our AI to create your database tailor made for your use cases, with text prompt. You can also create your database from list of 50+ templates on a single tap. All of that built for mobile using the Flutter Framework.
Demo project
This demo project consists of a Cupertino styled Widgets depending on 'package:flutter/cupertino.dart' in the CupertinoHomePage.dart and Google Material styled widgets in the MaterialHomePage.dart which depends on the import ‘package:flutter/material.dart’.
Our main.dart is simply a stateless widget that returns one of the homepage widgets based on the Platform.
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
if(Platform.isIOS) {
return CupertinoHomePage();
} else {
return MaterialHomePage();
}
}
}
Next, we will be writing a Dart script PlatformSpeficCodeRemover.dart, which when run once on the project will modify all source code. (Hence make sure to take copy of your project).
This Dart file will be using codemod library, and it only has to be run on your development machine and not going to be shipped along with the app source code. So first lets add codemod library as a dev dependency in pubspec.yaml like below,
dev_dependencies:
codemod: ^1.2.0
Create a Dart code file with the code from my github demo project. Let’s look at each section of the code and what it does below.
void main(List<String> args) async {
List<String> newpaths = [];
Iterable<String> paths = filePathsFromGlob(Glob('**.dart', recursive: true));
for (var value in paths) {
if(value.contains("PlatformSpecificCodeRemover.dart")) {
continue;
}
newpaths.add(value);
}
}
First we are preparing list of path of the files that we are going to modify. filePathsFromGlob() will return the path, but will also contain this script file PlatformSpecificCodeRemover.dart. Hence we exclude it.
The main function takes in args from the command line, we will be two arguments. 1. the platform we are going to build for “android” / “ios”, 2. we will be passing — yes-to-all which is an argument that is further passed down to runInteractiveCodemod() function from the codemod library.
if(args.contains("ios")) {
exitCode = await runInteractiveCodemod(
newpaths,
FullVisitorIOS(),
args: args,
defaultYes: true
);
}
The codemod prompts everytime when modifying the source files, hence — yes-to-all is used to skip the prompt and modify all the files at one go.
FullVisitorIOS is the class where source code reading and modification is done. This class extends RecursiveAstVisitor. AST is abstract syntax tree, which is the data structure holding meaningful representation of our source code main.dart, CupertinoHomePage.dart and MaterialHomePage.dart. For every if keyword found following methods of FullVisitorIOS will be called
- visitIfElement(IfElement node) — is called when single line if() statements are found like the ones below
if(Platform.isIOS)
sum+=5;
- visitIfStatement(IfStatement node) — is called when if blocks with curly braces or if and else blocks are found, like the ones below
if(Platform.isIOS) {
for(int i = 0; i < 5; i++)
sum+=5;
} else {
for(int i = 0; i < 5; i++)
sum+=10;
}
using these two methods, we can find all the if statements in our source code. For each if statement found from our source code, we check whether its platform specific by checking whether the statement strings contains the string “Platform.isIOS”.
if(node.expression.toString().contains("Platform.isIOS"))
when compiling the project for iOS we do not require all the else statements of any if block matching if(Platform.isIOS). So its removed using the yieldPatch() method from the mixin AstVisitingSuggestor.
YieldPatch accepts the new code as string, the starting postion and ending position of the string we wish to replace in our source files.
W.r.t this demo project, the if statement at Line 17 in main.dart code will be represented by the node instance of visitIfStatement(IfStatement node). If all code in main.dart is represented as string, the node.offset tells us the index before the word if and the node.elseStatement.end tells us the index after the curly brace of the else statement at line 21 of main.dart.
Simply put, when running the PlatformSpecificCodeRemover.dart using the dart run command on terminal for mac / cmd for windows
> dart run PlatformSpecificCodeRemover.dart --yes-to-all ios
will make below modification to the main.dart
In this demo project, FullVisitorAndroid is implemented to carry out similar modification for android. The complete code is hosted on github.com at os_specific_code_removal_guide with comments for the handled edge cases.
Conclusion
Discarding unneeded code can help reducing iOS binary size, especially when scaling the project to target multiple platforms like mac and windows. Hope this article was useful to you. Thanks for reading.
Top comments (0)