Hybrid Mobile App Development: Native, Cross‑Platform, and Flutter Solutions
This article explains how web front‑end engineers can build native mobile apps by comparing pure native development, cross‑platform frameworks like React Native and Flutter, and hybrid approaches that combine native code, Flutter, and WebView, while providing project structure, code examples, compilation steps, and best‑practice tips.
In recent years, advances in front‑end technology have lowered the cost for web engineers to develop native mobile applications, prompting many teams to explore independent app development using a mix of technologies.
Pure Native Development requires separate codebases for iOS (Swift/Objective‑C) and Android (Kotlin/Java), leading to duplicated effort, platform‑specific testing, and maintenance overhead.
Cross‑Platform Solutions aim to write a single codebase that runs on multiple platforms. Two representative frameworks are highlighted:
React Native builds on web container concepts, optimizing loading, parsing, and rendering while using native UI components for performance. However, it still relies on a JavaScript bridge, requiring developers to be familiar with both iOS and Android native APIs.
Flutter , driven by Google, uses a unified rendering engine (Skia) and a widget‑based architecture, providing consistent UI across iOS and Android without depending on platform widgets. Its Dart language supports both JIT (development) and AOT (release) compilation, offering fast debugging and native‑level performance.
Hybrid Development combines the strengths of native, Flutter, and WebView. Common patterns include:
Native + WebView: a native shell loads most UI via standard web technologies.
Native + Flutter: Flutter handles UI‑intensive screens while native modules implement performance‑critical features.
Native + Flutter + WebView: adds a WebView layer for rapidly changing pages, reducing iteration cost.
Below is an example project structure for a Flutter‑centric hybrid app (folders omitted for brevity):
app-project
|----android // Android native directory
| |--gradle
| |--build.gradle // Android config
| |--key.jks
| |--key.properties
| |--app
| |--libs
| |--src
| |--profile
| | |--AndroidManifest.xml
| |--main
| |--kotlin
| |--res
| |--AndroidManifest.xml
|----ios
| |--Gemfile
| |--Podfile // iOS pod dependencies
| |--Flutter
| |--scripts
| | |--AppIcon.sh
| | |--flutterbuild.sh
| | |--setup.sh
| |--Runner
| |--Info.plist
| |--main.m
| |--Assets.xcassets
| |--Base.lproj
| |--AppDelegate.h
| |--AppDelegate.m
|----lib // Flutter workspace
| |--assets // resources
| |--config
| |--jsBridge // WebView bridge
| | |--live
| | |--login
| | |--cache
| |--pages // Flutter pages
| | |--live
| | |--loading
| | |--webViewPage
| | |--login
| |--routers // routing logic
| | |--routers.dart
| |--utils // utilities
| | |--cache.dart
| | |--color.dart
| | |--network.dart
| | |--events.dart
| |--widgets // reusable widgets
| | |--routers.dart
| |--main.dart // entry point
|----plugins // native plugins
| |--flutter-inappwebview // WebView library
| |--flutter-login
|----test
|----web
|----pubspec.yaml // Flutter configA simple Flutter page implementation demonstrates the declarative UI style:
// Page example
import 'package:flutter/widgets.dart';
class MyAPP extends StatelessWidget {
@override
Widget build(BuildContext context) {
return const Center(child: Text('Hello Qun'));
}
}
void main() => runApp(MyAPP());Routing in Flutter can be handled via basic or named routes. Basic route example:
// Basic route
class FirstPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return RaisedButton(
onPressed: () => Navigator.push(context, MaterialPageRoute(builder: (context) => SecondPage())),
);
}
}
class SecondPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return RaisedButton(
onPressed: () => Navigator.pop(context),
);
}
}Named route example:
// Named route
MaterialApp(
routes: {
"uri_page": (context) => UriPage(),
},
);
Navigator.pushNamed(context, "uri_page");Plugin configuration is done in pubspec.yaml :
version: 1.0.30+1
environment:
sdk: ">=2.12.0-0 <3.0.0"
flutter: ">=1.22.2"
dependencies:
flutter:
sdk: flutter
permission_handler: ^7.1.0
flutter_inappwebview:
path: ./plugins/flutter_inappwebview
joymo_app_upgrade:
path: ./plugins/joymo_app_upgrade
tal_login:
path: ./plugins/tal_login
student_101_live:
path: ./plugins/student_101_live
dev_dependencies:
flutter_test:
sdk: flutterNative‑Flutter communication uses a MethodChannel . Flutter side:
// Define channel
static const MethodChannel _channel = MethodChannel('student_101_live');
static Future
showLive(Map
? map) async {
try {
await _channel.invokeMethod('showLiveDialog', map);
} on MissingPluginException catch (e) {
print('MissingPluginException: ${e.message}');
} on PlatformException catch (e) {
print('invoke method failed: ${e.message}');
}
}iOS side (generated plugin class):
// iOS plugin implementation
@implementation Student101LivePlugin
+ (void)registerWithRegistrar:(NSObject
*)registrar {
FlutterMethodChannel* channel = [FlutterMethodChannel methodChannelWithName:@"student_101_live" binaryMessenger:[registrar messenger]];
Student101LivePlugin* instance = [[Student101LivePlugin alloc] init];
[registrar addMethodCallDelegate:instance channel:channel];
}
- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
if ([@"showLiveDialog" isEqualToString:call.method]) {
if (call.arguments != nil) {
[self initLiveView:call.arguments];
}
result(@"iOS showLiveDialog");
} else {
result(FlutterMethodNotImplemented);
}
}
@endCompilation and release differ per platform. For Android, typical steps are:
Flutter clean
Flutter pub get
Flutter build apk --release // or --debugFor iOS, after cleaning and fetching dependencies, use:
Flutter clean
Flutter pub get
Flutter build ipa --release // or --debug / --profileBoth platforms ultimately rely on their native build tools (Android Studio, Xcode) to package the final binaries.
Common pitfalls include overusing animations (which consume main‑thread resources), interaction conflicts between native, Flutter, and WebView layers, coordination challenges during integration testing, and inconsistencies caused by differing Flutter SDK versions across developers.
In summary, modern cross‑platform frameworks have made it feasible for web front‑end engineers to deliver native‑like mobile experiences. By starting with Flutter for stable screens, using native modules for performance‑critical features, and falling back to WebView for rapidly changing content, teams can balance development speed, cost, and user experience.
TAL Education Technology
TAL Education is a technology-driven education company committed to the mission of 'making education better through love and technology'. The TAL technology team has always been dedicated to educational technology research and innovation. This is the external platform of the TAL technology team, sharing weekly curated technical articles and recruitment information.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.