A production-ready hybrid mobile application demonstrating seamless integration between native Android (Kotlin + Jetpack Compose) and Flutter Module with bidirectional communication.
This project showcases how to build a hybrid mobile application where:
- Native Android screens are built with Jetpack Compose and MVVM architecture
- Flutter screens follow Clean Architecture with BLoC pattern
- Both layers communicate bidirectionally via Platform Channels
- Shared FlutterEngine for optimal performance
- Complete CI/CD pipeline with GitHub Actions and CircleCI
- Hybrid Navigation: Seamless transitions between native and Flutter screens
- Bidirectional Communication: Native ↔ Flutter event exchange via MethodChannel
- Appliance Management: Browse appliances in native UI, view details in Flutter
- State Management:
- Android: MVVM with StateFlow/SharedFlow
- Flutter: BLoC pattern with Freezed
- Dependency Injection:
- Android: Dagger Hilt
- Flutter: GetIt
- Testing: Comprehensive unit tests for Flutter BLoC components
- CI/CD: Automated builds and testing with GitHub Actions & CircleCI
- Language: Kotlin
- UI: Jetpack Compose
- Architecture: MVVM (Model-View-ViewModel)
- DI: Dagger Hilt
- Networking: Retrofit + OkHttp
- Async: Coroutines + Flow
- Build: Gradle with Kotlin DSL
- Language: Dart
- Architecture: Clean Architecture with BLoC
- State Management: flutter_bloc + Freezed
- DI: GetIt
- Networking: Dio
- Testing: mockito + bloc_test
- Code Generation: build_runner, freezed
- Platform Channels: MethodChannel for bidirectional method invocation
- Event Channel: EventChannel for streaming (setup for future use)
- Data Format: JSON for cross-platform data serialization
┌──────────────────────────────────────────────────────┐
│ Android Native Layer │
│ ┌────────────────────────────────────────────────┐ │
│ │ DashboardActivity (Jetpack Compose) │ │
│ │ - Appliance list │ │
│ │ - Image carousel │ │
│ │ - Support cards │ │
│ └────────────────────────────────────────────────┘ │
│ │ Intent │
│ ▼ │
│ ┌────────────────────────────────────────────────┐ │
│ │ HybridFlutterActivity │ │
│ │ - Hosts FlutterFragment │ │
│ │ - Manages lifecycle │ │
│ └────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────┘
│
│ MethodChannel
▼
┌──────────────────────────────────────────────────────┐
│ Flutter Module Layer │
│ ┌────────────────────────────────────────────────┐ │
│ │ HomePage │ │
│ │ - Flutter home screen │ │
│ └────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────────────────────────────┐ │
│ │ ApplianceDetailPage │ │
│ │ - Appliance details │ │
│ │ - Interactive controls │ │
│ └────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────┘
Android Native Platform Channel Flutter
───────────── ───────────────── ───────
DashBoardViewModel
│
│ 1. Start HybridFlutterActivity
├──────────────────────────────────────────────>
│
│ 2. Send "navigate" event
│ + appliance data
├────────────────────────> MethodChannel
│ │
│ │ 3. Receive event
│ └────────────────> HybridBloc
│ │
│ │ 4. Navigate
│ │ to route
│ ▼
│ ApplianceDetailPage
│ │
│ 5. Send "navigateBack" │
│ <────────────────────────┤
│ 6. Receive event │
│<─────────────────────────┤
│
│ 7. Close activity
│ finish()
sample_hydrid_android/
├── app/ # Android native app
│ ├── src/main/java/com/example/myapplication/
│ │ ├── core/
│ │ │ ├── MainApplication.kt
│ │ │ ├── service/
│ │ │ │ ├── HybridEventBus.kt # Event communication
│ │ │ │ └── HydridNavigateService.kt
│ │ │ ├── theme/ # Compose theme
│ │ │ └── utils/
│ │ ├── data/
│ │ │ ├── model/ # Data models
│ │ │ ├── remote/ # API service
│ │ │ └── repository/
│ │ ├── di/ # Dagger Hilt modules
│ │ │ ├── FlutterEngineModule.kt
│ │ │ ├── NetworkModule.kt
│ │ │ └── ServiceModule.kt
│ │ └── ui/
│ │ ├── components/ # Reusable Compose components
│ │ └── feature/
│ │ ├── dashboard/ # Native dashboard
│ │ └── hydrid/ # Flutter host activity
│ └── build.gradle.kts
│
├── flutter_module/ # Flutter module
│ ├── lib/
│ │ ├── core/
│ │ │ ├── di/
│ │ │ │ └── injection_container.dart
│ │ │ ├── routes/
│ │ │ │ ├── app_routes.dart
│ │ │ │ └── route_mapper.dart
│ │ │ ├── services/
│ │ │ │ ├── hybrid_bus_service.dart
│ │ │ │ └── navigation_service.dart
│ │ │ └── theme/
│ │ ├── data/ # Data layer
│ │ ├── domain/ # Domain layer
│ │ │ ├── api/
│ │ │ ├── model/
│ │ │ └── usecases/
│ │ └── ui/ # Presentation layer
│ │ ├── home/
│ │ │ ├── bloc/
│ │ │ └── home_page.dart
│ │ └── appliance/
│ │ ├── bloc/
│ │ └── appliance_detail_page.dart
│ ├── test/ # Unit tests
│ └── pubspec.yaml
│
├── .github/workflows/
│ └── build-apk.yml # GitHub Actions CI
├── .circleci/
│ └── config.yml # CircleCI configuration
├── HYBRID_INTEGRATION.md # Detailed integration docs
├── QUICK_REFERENCE.md # Quick reference guide
└── README.md # This file
- Android Studio: Arctic Fox or later
- Flutter SDK: 3.36.5 or later
- Dart SDK: Included with Flutter
- JDK: 17 or later
- Gradle: 8.0+
-
Clone the repository
git clone https://github.com/yourusername/sample_hydrid_android.git cd sample_hydrid_android -
Set up Flutter module
cd flutter_module flutter pub get cd ..
-
Generate Flutter code (for Freezed, Mockito, etc.)
cd flutter_module dart run build_runner build --delete-conflicting-outputs cd ..
-
Open in Android Studio
- Open the root project in Android Studio
- Wait for Gradle sync to complete
./gradlew assembleDebug./gradlew assembleReleasecd flutter_module
flutter build aarcd flutter_module
flutter testcd flutter_module
flutter test --coveragecd flutter_module
dart run build_runner build --delete-conflicting-outputs./gradlew test./gradlew connectedAndroidTestThis project includes two CI/CD configurations:
- Workflow: .github/workflows/build-apk.yml
- Triggers: Push to
main,developbranches - Steps:
- Checkout code
- Set up Java 17
- Set up Flutter
- Install Flutter dependencies
- Generate code (build_runner)
- Run Flutter tests
- Build Android APK
- Upload APK artifact
- Config: .circleci/config.yml
- Triggers: Push to any branch
- Steps:
- Checkout code
- Restore Flutter cache
- Install Flutter SDK
- Install dependencies
- Generate code
- Run tests with coverage
- Build APK
- Store artifacts
- HYBRID_INTEGRATION.md - Complete guide to the hybrid architecture
- Communication channels
- Android native implementation
- Flutter module implementation
- Navigation patterns
- Event handling
- Best practices
- Troubleshooting
- QUICK_REFERENCE.md - Quick code snippets and patterns
- Event communication examples
- Navigation patterns
- Dependency injection
- Debugging tips
- Checklists
The app uses a cached FlutterEngine for better performance:
// Pre-warm at startup
val engine = FlutterEngine(context).apply {
dartExecutor.executeDartEntrypoint(
DartExecutor.DartEntrypoint.createDefault()
)
}
FlutterEngineCache.getInstance().put(Constants.flutterEngineName, engine)
// Reuse in activities
FlutterFragment.withCachedEngine(Constants.flutterEngineName).build()Events are sent via MethodChannel with JSON data:
// Android → Flutter
hybridEventBus.sendEventToHydrid("navigate", mapOf(
"routeName" to "/appliance/1",
"id" to "1",
"name" to "Refrigerator"
))
// Flutter → Android
await hybridBusService.sendEventToNative('navigateBack', {});- Native screen starts
HybridFlutterActivity - Activity sends navigation event with route and data
- Flutter receives event and navigates to route
- Flutter sends back event to close activity
- User taps appliance card in native dashboard (Compose)
DashBoardViewModelstartsHybridFlutterActivity- Sends "navigate" event with appliance data
- Flutter
HybridBlocreceives event - Navigates to
ApplianceDetailPagewith data - User sees appliance details in Flutter UI
- User taps back button in Flutter screen
ApplianceDetailBlocsends "navigateBack" event- Android
HybridFlutterViewModelreceives event - Emits to
closeActivityflow HybridFlutterActivityfinishes- User returns to native dashboard
Issue: FlutterEngine not found
Solution: Ensure FlutterEngineModule is providing the engine before
creating FlutterFragment
Issue: Events not received in Flutter
Solution:
1. Verify channel name matches: "hybrid_channel"
2. Call hybridEventBus.register() in Activity
3. Check registerListener() is called in BLoC
Issue: Activity doesn't close on back
Solution: Ensure closeActivity flow is collected in Activity:
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
hydridViewModel.closeActivity.collect { finish() }
}
}
- FlutterEngine caching: Reduces cold start time from ~2s to ~200ms
- Event queuing: Prevents event loss during initialization
- Lazy singletons: Services are created only when needed
- StateFlow: Efficient state management with replay cache
Contributions are welcome! Please follow these steps:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
- Android: Follow Kotlin coding conventions
- Flutter: Follow Effective Dart
- Use meaningful variable names
- Add comments for complex logic
- Write unit tests for new features
This project is licensed under the MIT License - see the LICENSE file for details.
- Flutter team for the excellent Flutter Module architecture
- Dagger Hilt for powerful dependency injection
- BLoC pattern for predictable state management
- Jetpack Compose for modern Android UI
For questions or feedback, please open an issue on GitHub.
Built with ❤️ using Kotlin, Flutter, and modern Android development practices