{"id":19055572,"url":"https://github.com/duytq94/cross-platform-how-it-works","last_synced_at":"2025-07-14T12:38:13.288Z","repository":{"id":96138997,"uuid":"560012032","full_name":"duytq94/cross-platform-how-it-works","owner":"duytq94","description":"The article about mobile app cross-platform, behind the scenes, and how it works","archived":false,"fork":false,"pushed_at":"2025-02-24T09:34:35.000Z","size":816,"stargazers_count":62,"open_issues_count":0,"forks_count":29,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-04-02T09:08:08.140Z","etag":null,"topics":["android","cross-platform","flutter","ios","native","react-native"],"latest_commit_sha":null,"homepage":"","language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/duytq94.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-10-31T15:02:06.000Z","updated_at":"2025-02-24T09:34:39.000Z","dependencies_parsed_at":"2025-02-28T23:08:42.808Z","dependency_job_id":"02302cd2-8b33-4448-9f4c-7b9429c29a45","html_url":"https://github.com/duytq94/cross-platform-how-it-works","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/duytq94%2Fcross-platform-how-it-works","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/duytq94%2Fcross-platform-how-it-works/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/duytq94%2Fcross-platform-how-it-works/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/duytq94%2Fcross-platform-how-it-works/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/duytq94","download_url":"https://codeload.github.com/duytq94/cross-platform-how-it-works/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248027411,"owners_count":21035594,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["android","cross-platform","flutter","ios","native","react-native"],"created_at":"2024-11-08T23:46:10.354Z","updated_at":"2025-04-09T11:11:53.742Z","avatar_url":"https://github.com/duytq94.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"# Mobile app cross-platform, how it works\n\n## Preamble\nHave you ever wondered why\n- Flutter and React Native (RN) can build apps for both Android and iOS.\n- Supporting \"hot reload\" to quickly show affected code changes.\n- Performance is still not as good as native.\n- You’ve heard somewhere that says Flutter has performance close to native because Dart code is compiled to native code, while React Native has less performance because it has to go through the bridge (which sometimes has bottleneck).\n\nTo come clean, we're going through their architecture \u0026 compilation process.\n\nBefore starting, you can simply understand that the compilation (or interpretation) process will convert your programming language (e.g. Kotlin, Swift, Dart, JavaScript...), finally into executable machine code (which the CPU can execute).\n\n\u003e And for me, calling a language interpreted or compiled is not a well-defined concept. Technically it all matters depending on the implementation, not part of the language specification. Let read to the end of this article and you know why I said that.\n\nWe should go through native app (Android, iOS) compilation process to overview behind the scenes, then you’ll get about cross-platform (Flutter, React Native) easier.\n\n## Android\nAndroid can be written by Java or Kotlin, so the Android compilation process, in short, is based on Java/Kotlin compilation process.\n\n- File .java or .kt is compiled by JAVAC (Java compiler)/Kotlin compiler -\u003e Java byte code (file .class).\n- With a normal Java app (e.g. run on Windows, Linux), there will be a JVM (Java virtual machine) converting these files to machine code and running by CPU.\n- But with Android, this .class file will be minimized by proguard, then is compiled by Dex compiler -\u003e Dex byte code (file .dex) -\u003e .apk.\n- When you launch (start up procedure) an Android app, file .dex will be converted to machine code by DVK/ART, and fed into memory, then executed by CPU.\n\n![android compile](photos/android.png)\n\nDVK (Dalvik Virtual Machine) or ART (Android Runtime) is a virtual machine available on OS, to help you run Dalvik bytecode on Android devices.\n\nThe difference between DVK vs ART is that ART (introduced from Android 4.4) was built as a replacement for DVM because it uses AOT (Ahead Of Time) compilation, while DVM uses JIT (Just-In-Time). \nJIT compilation does the compilation during the execution of a program (every time you launch an app). While AOT compilation does the compilation when the app is first install -\u003e this is key point ART makes an Android app running faster than DVK.\n\n## iOS\niOS compilation process, in short, based on Swift compilation process (actually there was some previous step with Xcode, but we won’t focus on this article).\n\nThe swift compiler has two dimensions: Frontend and Backend (and don’t think this is web client and server).\n\n- Frontend: does lexical analysis, parsing and semantic analysis from frontend and passes rest of the phases to the backend.\n- Backend: SwiftC (swift compiler) converts code to the form of AST (Abstract Syntax Tree) -\u003e then goes through semantic analysis and is converted into Swift Intermediate Language (SIL).\n- This code goes through analysis and is optimized along with LLVM IR (Intermediate Representation).\n- Finally, LLVM converts them into assembly code and finally ends up in an executable file -\u003e .ipa\n\n![iOS compile](photos/ios.png)\n\n\nLLVM (Low Level Virtual Machine) is a library for programmatically creating machine-native code.\n\niOS app is fully machine code when installed to the device, this is an advantage of iOS optimization compared to other platforms.\n\n\u003e From native's perspective, all React Native/Flutter screens just only take an Activity/View Controller. It means when you navigate between React Native/Flutter screens, you're moving inside React Native/Flutter's navigator, not `startActivity` or `pushViewController` in native code (conceivably it is similar in-app webview in this case).\n\n## Flutter\nNow it also supports web and desktop app, but in this article, we just focus on mobile apps (Android \u0026 iOS).\n\nWe know that developers can write Flutter apps by Dart, but Flutter compilation process will be a little bit different because not only Dart is compiled, but also C/C++ and more, why?\n\n### Architecture Overview\n\n![flutter architecture](photos/flutter-arch.png)\n\nYou’ll see that Flutter has 3 layers that were written in different languages, and they will explain how a Flutter app works.\n\n- Embedder is written in a language that is appropriate for the platform: currently Java and C++ for Android, Objective-C/Objective-C++ for iOS and macOS, and C++ for Windows and Linux. Embedder provides an entrypoint, coordinates with the underlying operating system for access to services like rendering surfaces, accessibility, and input; and manages the message event loop. \n- Engine, which is mostly written in C++ and supports the primitives necessary to support all Flutter applications. The engine is responsible for rasterizing composited scenes whenever a new frame needs to be painted. It provides the low-level implementation of Flutter’s core API, including graphics (through Impeller), text layout, file and network I/O, accessibility support, plugin architecture, and a Dart runtime and compile toolchain.\n- Framework, where most developers work, to build a Flutter app.\n\nDifferent from React Native (which converts your JS component to native component), Flutter uses Impeller to render an entire UI widget by itself. So it means you can layout the same UI for both Android \u0026 iOS (including UX).\n\nWhen comparing between Flutter/Impeller and RN/native component, although I haven't done specific measurement statistics yet, but if you come to my articles [flutter-fb-reactions-animation](https://github.com/duytq94/flutter-fb-reactions-animation) \u0026 [react-native-fb-reactions-animation](https://github.com/duytq94/react-native-fb-reactions-animation), you can feel that Flutter brings to a smoother animation than RN.\n\nThis happen because React Native have to calculate animation value at JS thread then send to Native (UI) thread on every frame, if JS thread is blocked =\u003e drop frames =\u003e cause jank. Using useNativeDriver could help, it move all steps to native, but not support all cases.\n\nHow does hot reload work?\nBecause Dart support both JIT (debug mode) and AOT (release mode), so:\n- When debugging, Flutter uses Dart VM with JIT mode, in short, it means that the developer can change the code at runtime, and only the new code that has been changed/inserted will be changed at the app, without the need to compile it again.\n- When apps are ready to be deployed to production, the Dart ahead-of-time (AOT) compiler can compile to native ARM or x64 machine code. Your AOT-compiled app launches with consistent, short startup time.\n\nThis couldn’t happen in AOT languages (like Java/Kotlin on Android \u0026 Objective-C/Swift on iOS), since there’s no runtime environment to handle it.\n\nFlutter compilation process basically means compile 3 layers we mentioned above, and will be different in Android and iOS.\n\n### Compilation process\n### Flutter Android\n- The engine’s C and C++ code are compiled with Android’s NDK.\n- The Dart code is ahead-of-time (AOT) compiled by Dart Compiler into native, ARM, and x86 libraries (most current android devices run on ARM, the latest x86 device is Asus zenfone 2 with intel Atom released 2015).\n- Those libraries are included in a “runner” Android project, and the whole thing is built into an .apk.\n- When launched, the app loads the Flutter library. Any rendering, input, or event handling, and so on, is delegated to the compiled Flutter and app code.\n\n![flutter android compile](photos/flutter-android.png)\n\n### Flutter iOS\n- The engine’s C and C++ code are compiled with LLVM.\n- The Dart code is ahead-of-time (AOT) compiled by Dart Compiler into a native, ARM library.\n- That library is included in a “runner” iOS project, and the whole thing is built into an .ipa.\n- When launched, the app loads the Flutter library. Any rendering, input or event handling, and so on, are delegated to the compiled Flutter and app code.\n\n![flutter ios compile](photos/flutter-ios.png)\n\n### Performance\n- App built by Flutter not only includes your logic code (Dart) but also includes Embedder \u0026 Engine, so the app size will increase by around 5 MB compared to native (Android/iOS). Check the section [App size comparison](#app-size-comparison).\n- Even though it's built into native code, Flutter still need engine (role as a bridge) when (and only, not all cases like RN bridge) handling task which connect with native API (camera, audio, sensor...) through platform channels, and it's asynchronously.\n\n## React Native\n### Architecture Overview\n![rn architecture](photos/rn-arch.png)\n\n- JSC (JavaScriptCore, also called JS engine, written in C++) is a framework that allows JavaScript code to be run on mobile devices. On iOS devices, this framework is directly provided by the OS while Android devices don't have it, so React Native needs to bundle it along with the Android app.\n- React Native bridge (written in Java/C++) allows communication (by message JSON serialize, batch update, asynchronous) between JS thread (handle your React logic code in JS bundle) and Native thread.\n- Native (also called Main/UI) thread to handle UI rendering, user gestures...\n- Yoga (written in C/C++) is a cross-platform layout engine run on Shadow (also called Background/Layout) thread.\n\nThe image below describes communication between threads to handle a user action, for example scrolling a list\n![rn threading](photos/rn-threading.png)\n\nReact Native components will be converted to native (Android/iOS) view, for example in some core components:\n\n| RN component | Android view  | iOS view        |\n| ------------ | ------------- | --------------- |\n| `\u003cImage\u003e`    | `\u003cImageView\u003e` | `\u003cUIImageView\u003e` |\n| `\u003cText\u003e`     | `\u003cTextView\u003e`  | `\u003cUITextView\u003e`  |\n| ...          | ...           | ...             |\n\n### Compilation process\n- JS code will be packaged by Metro into a JS bundle.\n- JSC, Yoga, React Native bridge will be packaged along with app.\n- Generate APK/IPA.\n\n![rn compile](photos/rn-compile.png)\n\nJavaScript is an interpreted language, during debug mode, the JS code runs with Chrome (using V8 engine) instead of JSC, and communicates with native code via WebSocket. So it supports \"hot reload\" and allows us to see a lot of information on the Chrome debugging tools like network requests, console logs...\n\nReact Native (JS code part) doesn’t be compiled to native code, every time an app launches, JSC will execute JS code, and communicate with native modules through React Native bridge (which causes some performance issues).\n\n### Performance\n- JavaScript is an interpreted language, need virtual machine/engine to intepret every launch app.\n- All communication (render UI, using native modules...) between JS and native depends on the bridge with asynchronous -\u003e bottleneck, not ensure realtime update, or JS thread is blocked cause UI thread jank.\n\nBut recently, React Native release new Hermes (from ver 0.64 for both Androd \u0026 iOS) \u0026 New Architecture (from ver 0.68):\n- Hermes is a JavaScript engine (replacement for JSC) designed to optimize performance by reducing app launch time \u0026 precompiling JavaScript into efficient bytecode (meaning now Hermes compiles JS code -\u003e bytecode at app building time) -\u003e no more JIT (the answer to the question at the beginning of the article about defining a language as interpreted or compiled).\n- New Architecture:\n   - JSI (JavaScript interface) to replace The Bridge.\n   - JSI can using any JS Engine instead of bridge only compatible with JSC.\n   - Complete interoperability between all threads.\n   - Lazy loading with Turbo Modules.\n\n![rn new arch](photos/rn-new-arch.png)\n\n\n## App size comparison\nThese are the output of a **blank app** (I tested with their SDKs version around May 2020, and now maybe having a lot of improvements to optimize app size, but you can still refer to these figures for an overview).\n\n### Android\n![android size](photos/android-size.png)\n\n![android detail size](photos/android-detail-size.png)\n\nNative has a small size because it contains only your logic code.\n\n### Flutter\n![flutter size](photos/flutter-size.png)\n\n![flutter detail size](photos/flutter-detail-size.png)\n\nFlutter app has to package its engine \u0026 embedder along with your logic code.\n\nIgnore app-x86-release.apk, which only has 487 KB because Flutter doesn’t support this CPU instruction set.\n\n### React Native\n![rn size](photos/rn-size.png)\n\n![rn detail size](photos/rn-detail-size.png)\n\nReact Native app has to package its bridge, JSC (only on android) along with your logic code.\n\nYou can see that libjsc.so take 3.2 MB.\n\nAnd when optimizing with Hermes:\n\n![rn hermes size](photos/rn-hermes-size.png)\n\n![rn hermes detail size](photos/rn-hermes-detail-size.png)\n\nHermes replaces JSC, more compact, and take only 808 KB.\n\n### iOS\nAnd an .ipa size before \u0026 after adding simple Flutter module\n![ios size](photos/ios-size.png)\n\nCross-platform always needs more size than native (since they have to include engine, virtual machine, core framework...).\n\n## Which platform we should use?\nComparing performance between platforms to make decision on which platform to choose is not a good way, and also temporary because technology change quickly, instead, we should clear:\n- Pros \u0026 cons of each platform.\n- Business requirements of the project.\n- Developer's level, the human resource of the team.\n\nFrom there, we can make decisions that are \"appropriate\" to the situation and achieve optimal results.\n\n_Willing if you have any contribution or discussion._\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fduytq94%2Fcross-platform-how-it-works","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fduytq94%2Fcross-platform-how-it-works","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fduytq94%2Fcross-platform-how-it-works/lists"}