A production-ready Kotlin Multiplatform Mobile (KMM) application for the Entrepreneurship Cell (E-Cell) community, built with industry-standard architecture and best practices.
- Project Overview
- Features
- Tech Stack
- Architecture
- Project Structure
- Setup Instructions
- Implementation Details
- Contributing
E-Cell KMP is a cross-platform mobile application designed to provide a comprehensive platform for the Entrepreneurship Cell community. Built using Kotlin Multiplatform, it shares business logic across Android and iOS while maintaining native UI experiences.
- π± Cross-platform development (Android & iOS)
- ποΈ Industry-standard Clean Architecture
- π Offline-first approach with local caching
- π Secure authentication and data management
- β‘ High performance with reactive programming
- π¨ Modern UI with Jetpack Compose
-
β Authentication System
- Firebase Authentication (Email/Password)
- Secure signup and login flows
- Session management with automatic re-authentication
- Job-cancellation-safe operations with
NonCancellablecontext
-
β User Profile Management
- View and edit user profiles
- Account details with personal information
- Social links integration (LinkedIn, Instagram, Portfolio)
- Profile picture support
-
β Local Data Caching
- Room Database integration for offline support
- Local-first data loading strategy
- Automatic background sync
- SQLite-based persistent storage
-
β Team Members Directory
- Browse team members with staggered grid layout
- Filter by domain and position
- View detailed member profiles
- Click-to-view functionality
-
β Navigation System
- Bottom navigation bar
- Type-safe Compose Navigation
- Nested navigation graphs
- Deep linking support
-
β Loading States & Error Handling
- Material 3 loading indicators
- Comprehensive error messages
- Graceful failure handling
- User-friendly feedback
- π Events and glimpses showcase
- π Domain exploration
- π Meeting schedules for team members
- π Push notifications
- π Real-time updates
| Technology | Purpose |
|---|---|
| Kotlin Multiplatform | Shared business logic across platforms |
| Compose Multiplatform | Declarative UI framework |
| Kotlin Coroutines | Asynchronous programming |
| Kotlin Flow | Reactive data streams |
| Component | Implementation |
|---|---|
| Architecture | Clean Architecture (3-layer) |
| Presentation | MVVM with unidirectional data flow |
| Dependency Injection | Koin |
| Navigation | Compose Navigation with type-safe routing |
| State Management | StateFlow + Immutable State classes |
| Service | Purpose |
|---|---|
| Firebase Auth | User authentication |
| Firebase Firestore | Cloud NoSQL database |
| Room Database | Local SQLite caching |
| Firebase Storage | Image and file storage |
| Library | Purpose |
|---|---|
| Material 3 | Modern design system |
| Coil | Image loading and caching |
| Compose Foundation | Core UI components |
This project implements Clean Architecture with strict layer separation and dependency rules.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Presentation Layer β
β β’ Composable screens & components β
β β’ ViewModels (state management) β
β β’ UI State & Action classes β
β β’ Can access: Domain layer only β
ββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββ
β
β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Domain Layer β
β β’ Business logic & use cases β
β β’ Domain models (pure Kotlin) β
β β’ Repository interfaces β
β β’ Independent - no dependencies β
ββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββ
β
β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Data Layer β
β β’ Repository implementations β
β β’ Data sources (Remote & Local) β
β β’ DTOs & Mappers β
β β’ Can access: Domain layer only β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ββββββββββββββββββββββββββββββ
β Utility Layer β
β Accessible by all layers β
ββββββββββββββββββββββββββββββ
- Presentation β Domain β
- Data β Domain β
- All Layers β Utility β
- Domain β Presentation β
- Domain β Data β
- Presentation β Data β
@Composable
fun Screen(viewModel: ViewModel) {
val state by viewModel.state.collectAsStateWithLifecycle()
// UI renders based on state
when {
state.isLoading -> LoadingIndicator()
state.data != null -> DataDisplay(state.data)
state.error != null -> ErrorMessage(state.error)
}
// User actions sent to ViewModel
Button(onClick = { viewModel.onAction(Action.ButtonClicked) })
}// Domain layer - Interface
interface Repository {
suspend fun getData(): Result<Data, Error>
}
// Data layer - Implementation
class RepositoryImpl(
private val remoteSource: RemoteSource,
private val localSource: LocalSource
) : Repository {
override suspend fun getData(): Result<Data, Error> {
// Local-first strategy
return localSource.getData() ?: remoteSource.getData()
}
}sealed interface Result<out D, out E : Error> {
data class Success<out D>(val data: D) : Result<D, Nothing>
data class Error<out E : Error>(val error: E) : Result<Nothing, E>
}
// Usage
when (val result = repository.getData()) {
is Result.Success -> handleSuccess(result.data)
is Result.Error -> handleError(result.error)
}data class ScreenState(
val data: List<Item> = emptyList(),
val isLoading: Boolean = false,
val errorMessage: UiText? = null
)
sealed interface ScreenAction {
data object LoadData : ScreenAction
data class ItemClicked(val id: String) : ScreenAction
}EcellKMP/
βββ composeApp/
β βββ src/
β β βββ commonMain/kotlin/com/anantmittal/ecellkmp/
β β β βββ app/ # Application setup
β β β β βββ App.kt # Main app entry point
β β β β βββ navigation/ # Navigation graphs
β β β β
β β β βββ data/ # Data Layer
β β β β βββ database/ # Room database
β β β β β βββ EcellAccountsDao.kt
β β β β β βββ EcellAccountsEntity.kt
β β β β β βββ EcellAccountsDatabase.kt
β β β β βββ dto/ # Data Transfer Objects
β β β β β βββ AccountDTO.kt
β β β β βββ mappers/ # DTO β Model mappers
β β β β β βββ Mappers.kt
β β β β βββ network/ # Network data sources
β β β β β βββ authenticationsource/
β β β β β βββ EcellAuthSource.kt
β β β β β βββ FirebaseEcellAuthSource.kt
β β β β βββ repository/ # Repository implementations
β β β β βββ DefaultEcellRepository.kt
β β β β
β β β βββ domain/ # Domain Layer
β β β β βββ models/ # Domain models
β β β β β βββ AccountModel.kt
β β β β β βββ User.kt
β β β β β βββ LoginModel.kt
β β β β β βββ SignupModel.kt
β β β β βββ repository/ # Repository interfaces
β β β β βββ EcellRepository.kt
β β β β
β β β βββ presentation/ # Presentation Layer
β β β β βββ splash_screen/
β β β β β βββ SplashScreen.kt
β β β β βββ login_screen/
β β β β β βββ LoginScreen.kt
β β β β β βββ LoginViewModel.kt
β β β β β βββ LoginState.kt
β β β β β βββ LoginAction.kt
β β β β β βββ components/
β β β β βββ signup_screen/
β β β β β βββ SignupScreen.kt
β β β β β βββ SignupViewModel.kt
β β β β β βββ SignupState.kt
β β β β β βββ SignupAction.kt
β β β β βββ home_screen/
β β β β β βββ HomeScreen.kt
β β β β β βββ HomeViewModel.kt
β β β β β βββ HomeState.kt
β β β β β βββ HomeAction.kt
β β β β β βββ components/
β β β β β βββ EventGlimpseBanner.kt
β β β β β βββ TeamMembersList.kt
β β β β βββ account_screen/
β β β β β βββ AccountScreen.kt
β β β β β βββ AccountViewModel.kt
β β β β β βββ AccountState.kt
β β β β β βββ AccountAction.kt
β β β β β βββ components/
β β β β βββ meetings_screen/
β β β β βββ bottom_navigation/
β β β β βββ BottomNavigation.kt
β β β β
β β β βββ di/ # Dependency Injection
β β β β βββ Modules.kt
β β β β
β β β βββ utility/ # Utilities
β β β βββ domain/ # Domain utilities
β β β β βββ AppLogger.kt
β β β β βββ DataError.kt
β β β β βββ Result.kt
β β β β βββ Variables.kt
β β β βββ presentation/ # UI utilities
β β β βββ Colors.kt
β β β βββ UiText.kt
β β β βββ components/
β β β βββ LoadingIndicator.kt
β β β
β β βββ androidMain/ # Android-specific code
β β β βββ kotlin/
β β β βββ MainActivity.kt
β β β βββ App.android.kt
β β β
β β βββ iosMain/ # iOS-specific code
β β βββ kotlin/
β β βββ MainViewController.kt
β β
β βββ build.gradle.kts
β βββ google-services.json
β
βββ gradle/
β βββ libs.versions.toml # Centralized dependency versions
βββ build.gradle.kts
βββ settings.gradle.kts
βββ README.md
βββ RULES.md # Architecture rules and guidelines
- JDK 17 or higher
- Android Studio Hedgehog (2023.1.1) or newer
- Xcode 15+ (for iOS development)
- Kotlin 2.0.0+
- Gradle 8.0+
-
Create Firebase Project
- Go to Firebase Console
- Create a new project or use existing one
-
Add Android App
- Package name:
com.anantmittal.ecellkmp - Download
google-services.json - Place in
composeApp/directory
- Package name:
-
Add iOS App
- Bundle ID:
com.anantmittal.ecellkmp - Download
GoogleService-Info.plist - Place in
iosApp/iosApp/directory
- Bundle ID:
-
Enable Authentication
- Go to Firebase Console β Authentication
- Enable Email/Password sign-in method
-
Setup Firestore
- Go to Firebase Console β Firestore Database
- Create database in production mode
- Create collection:
team_members
-
Clone the repository
git clone https://github.com/yourusername/EcellKMP.git cd EcellKMP -
Open in Android Studio
- Open the project in Android Studio
- Wait for Gradle sync to complete
-
Add Firebase configuration files
- Place
google-services.jsonincomposeApp/ - Place
GoogleService-Info.plistiniosApp/iosApp/
- Place
-
Build the project
./gradlew build
./gradlew :composeApp:installDebugOr use the "Run" button in Android Studio
./gradlew :composeApp:iosSimulatorArm64TestOr open iosApp/iosApp.xcodeproj in Xcode and run
The app implements a robust local-first data loading strategy for optimal performance:
suspend fun loadAccount(email: String): Result<AccountModel, DataError.Remote> {
// Step 1: Try local cache first (instant load)
when (val localResult = loadAccountLocally(email)) {
is Result.Success -> return Result.Success(localResult.data)
is Result.Error -> // Continue to remote
}
// Step 2: Fetch from remote and cache
when (val remoteResult = loadAccountRemotely(email)) {
is Result.Success -> {
cacheLocally(remoteResult.data) // Cache for next time
return Result.Success(remoteResult.data)
}
is Result.Error -> return Result.Error(remoteResult.error)
}
}Benefits:
- β‘ Instant load from cache (subsequent opens)
- π Automatic background sync
- π΄ Offline support
- π― Reduced network calls
Critical operations (signup, login) use NonCancellable context to prevent job cancellation:
override suspend fun signup(signupModel: SignupModel): Result<AccountModel, DataError.Remote> {
return withContext(NonCancellable) {
// Firebase auth signup
// Firestore account creation
// Local caching
// All operations complete even if screen navigates away
}
}Why this matters:
- β Prevents "Job was cancelled" errors
- β Ensures data integrity
- β Completes Firestore writes even during navigation
- β Reliable account creation
All critical operations have detailed logging for debugging:
AppLogger.d(TAG, "Starting signup for email: ${email}")
AppLogger.d(TAG, "Firestore: Query returned ${documents.size} documents")
AppLogger.e(TAG, "Failed to load account: ${error}")Log levels:
d- Debug informatione- Error conditions- All logs tagged with
Variables.TAG = "xyz"
Consistent loading indicators across the app:
Box(modifier = Modifier.fillMaxSize()) {
// Content
if (state.isLoading) {
LoadingIndicator() // Centered Material 3 loader
}
}- Follow Clean Architecture rules (see
RULES.md) - Use existing patterns for consistency
- Write comprehensive logging for debugging
- Handle all error cases properly
- Test on both platforms before committing
- Follow Kotlin coding conventions
- Use meaningful variable names
- Add KDoc comments for public APIs
- Keep functions small and focused
Use conventional commits format:
feat: add team member filtering
fix: resolve job cancellation in signup
docs: update README with setup instructions
refactor: improve repository caching logic
This project is licensed under the MIT License - see the LICENSE file for details.
- Anant Mittal - Project Lead & Developer
- Kotlin Multiplatform team for the amazing framework
- Firebase for backend services
- Jetpack Compose team for the UI toolkit
- E-Cell KIET for the opportunity
Made with β€οΈ using Kotlin Multiplatform
- β Team Members Display
- β Bottom Navigation
- β Nested Navigation Graphs (Auth, Normal User, Team)
- β Splash Screen
- β Event Glimpse Banner
- π Events List & Details
- π Domains Exploration
- π Team Member Profiles
- π Meeting Management (Team Access)
- π Image Loading (Network images)
- π Push Notifications (FCM)
- π Profile Editing
App
βββ Auth Nav Graph
β βββ Login Screen
β βββ Signup Screen
βββ Normal User Nav Graph
β βββ Home Screen
β βββ Account Screen
βββ Team Nav Graph
βββ Home Screen
βββ Meetings Screen
βββ Account Screen
- Android Studio (latest version)
- Xcode (for iOS development)
- JDK 17 or higher
- Firebase project setup
-
Clone the repository
git clone <repository-url> cd EcellKMP
-
Configure Firebase
- Add
google-services.jsontocomposeApp/ - Add
GoogleService-Info.plisttoiosApp/iosApp/
- Add
-
Open in Android Studio
- Open the project in Android Studio
- Wait for Gradle sync to complete
-
Run the app
- Android: Select Android run configuration and run
- iOS: Select iosApp run configuration and run
# Build Android
./gradlew :composeApp:assembleDebug
# Build iOS
./gradlew :composeApp:linkDebugFrameworkIosArm64- Follow Kotlin Coding Conventions
- Use meaningful variable and function names
- Keep functions small and focused
- Write comments for complex logic
- Use Compose best practices (remember, LaunchedEffect, etc.)
The project uses Koin for dependency injection:
// Module definition
val appModule = module {
single<EcellRepository> { DefaultEcellRepository(get(), get()) }
viewModel { HomeViewModel() }
}// State
data class HomeState(
val teamMembers: List<AccountModel> = emptyList(),
val isLoading: Boolean = false,
val errorMessage: UiText? = null
)
// Actions
sealed interface HomeAction {
data class OnTeamMemberClick(val accountModel: AccountModel) : HomeAction
}
// ViewModel
class HomeViewModel : ViewModel() {
private val _state = MutableStateFlow(HomeState())
val state = _state.asStateFlow()
fun onAction(action: HomeAction) { /* handle action */
}
}- Unit tests for ViewModels and Use Cases
- Repository tests with mock data sources
- UI tests for critical user flows (planned)
When contributing, please:
- Follow the architecture rules defined in RULES.md
- Write clean, maintainable code
- Add appropriate tests
- Update documentation as needed
- Create descriptive commit messages
[Add License Information]
For any queries or issues, please contact the E-Cell development team.