π App Introduction: Social media platform connecting language learners to share posts and interact with each other
π Duration: May 16, 2025 ~ May 27, 2025 (2 weeks)
π± Platform: Flutter cross-platform app (Android, iOS)
π₯ Team Size: 4 developers
πΌ Role: Team Leader, CI/CD pipeline, authentication system, profile management, feed filtering, Google Maps integration, onboarding flow
π οΈ Key Technologies: Flutter
Firebase
Riverpod
Clean Architecture
Google OAuth
Firestore
Cloud Functions
GitHub Actions
VWorld API
π GitHub: zero-to-one-flutter/flutter-share-lingo
π Play Store: ShareLingo on Play Store
βββ app/ # App-wide settings, constants, and themes
β βββ constants/ # App constant definitions
β β βββ app_colors.dart # Color scheme definitions
β β βββ app_constants.dart # Constant values (language lists, tags, etc.)
β β βββ app_styles.dart # Style definitions
β βββ theme.dart # App theme configuration
βββ core/ # Core functionality and utilities
β βββ exceptions/ # App-wide exception classes
β βββ extensions/ # Extension method definitions
β βββ providers/ # Common providers
β βββ ui_validators/ # UI validation utilities
β βββ utils/ # Utility functions
β βββ dialogue_util.dart # Dialog utilities
β βββ format_time_ago.dart # Time formatting utilities
β βββ general_utils.dart # General utility functions
β βββ geolocator_util.dart # Location utilities
β βββ logger.dart # Logging utilities
β βββ map_url_util.dart # Map URL generation utilities
β βββ navigation_util.dart # Navigation utilities
β βββ snackbar_util.dart # Snackbar utilities
β βββ throttler_util.dart # Throttling utilities
βββ data/ # Data layer and data access
β βββ data_source/ # Data source classes
β βββ dto/ # Data Transfer Objects
β βββ repository/ # Repository implementations
βββ domain/ # Business logic and entities
β βββ entity/ # Domain entities
β βββ repository/ # Repository interfaces
β βββ usecase/ # Use cases
βββ presentation/ # UI layer
β βββ pages/ # App screens
β β βββ home/ # Home screen (example)
β β β βββ home_page.dart # Home page
β β β βββ home_view_model.dart # Home view model
β β β βββ widgets/ # Home screen widgets
β βββ widgets/ # Common widgets
β βββ user_global_view_model.dart # Global user view model
βββ main.dart # App entry point
flutter analyze
code quality checks on every pull request, reducing manual review time by 50%GitHub Artifacts
when pushing to test-apk
branchFirebase
files and storing them in GitHub Secrets
Google Sign-In
with Firebase Authentication
Google Maps Static API
VWorld API
to convert GPS coordinates into district-level location dataAll
tab: Chronological feed of all user postsRecommended
tab: Shows posts from users whose native language is your target language and whose target language is your native language, enabling mutual language exchangePeers
tab: Displays posts from users learning the same language combination as you, providing study motivation and shared experiencesNearby
tab: Features posts from users in the same district, enabling potential offline meetupsSharedPreferences
to remember consent status and avoid repeated promptsURL Launcher
for in-app access to terms and privacy policy, also available from the settings screenRiverpod
dependency injection allowing all classes to receive dependencies through constructors, enabling easy Mock object injection for testingRepository
pattern for data abstraction, allowing potential migration from Firebase
to other backends without business logic changesMocktail
to isolate external dependencies, enabling tests to run without Firebase
or Google Sign-In
connectionsFirebase Crashlytics
.env
files for environment-specific configuration while protecting sensitive data1. Clean Architecture and Dependency Injection Pattern
Requirements
Team needed a scalable structure to manage complex features, minimize code conflicts, and support future expansion and maintenance
Decision
Adopted Clean Architecture
with Riverpod
dependency injection
// Repository Interface (Domain Layer)
abstract class AuthRepository {
Future<AppUser?> signInWithGoogle();
Future<void> signOut();
Stream<String?> authStateChanges();
}
// Repository Implementation (Data Layer)
class AuthRepositoryImpl implements AuthRepository {
final GoogleSignInDataSource _googleSignIn;
final FirebaseAuthDataSource _firebaseAuth;
final UserDataSource _userDataSource;
AuthRepositoryImpl(this._googleSignIn, this._firebaseAuth, this._userDataSource);
// Implementation...
}
// Dependency Injection Setup
final authRepositoryProvider = Provider<AuthRepository>(
(ref) => AuthRepositoryImpl(
ref.read(googleSignInDataSourceProvider),
ref.read(firebaseAuthDataSourceProvider),
ref.read(userFirestoreDataSourceProvider),
),
);
2. GeoPoint Extension for Distance Calculations
Requirements
Need intuitive way to show users actual proximity from potential language partners
Decision
Created GeoPoint Extension
with distance calculation methods
GeoPoint
objects,enabling clean usage like geoPoint.distanceFrom(otherPoint)
anywhere in the codebasedistanceBetween
method for precise calculations accounting for Earthβs curvature"3.2 km"
) handled in presentation layer, keeping logic focusedextension GeoPointExtensions on GeoPoint {
double distanceFrom(GeoPoint other) {
final distanceInMeters = Geolocator.distanceBetween(
latitude, longitude, other.latitude, other.longitude,
);
return distanceInMeters / 1000;
}
}
// Usage in UserGlobalViewModel
String? calculateDistanceFrom(GeoPoint? otherLocation) {
final userLocation = state?.location;
if (userLocation == null || otherLocation == null) return null;
final distanceKm = userLocation.distanceFrom(otherLocation);
return '${distanceKm.toStringAsFixed(1)} km';
}
1. Onboarding User Experience Optimization
Problem
Location permission denial caused app usage restrictions or errors, leading to user abandonment. Lack of distinction between temporary and permanent denial prevented appropriate guidance messaging.
success
: Location acquired successfullydeniedTemporarily
: User can be asked again β show retry guidancedeniedForever
: User blocked permission β provide settings navigation instructionserror
: Technical issue β offer retry or alternative pathenum LocationStatus { success, deniedTemporarily, deniedForever, error }
Future<(LocationStatus, Position?)> getPosition() async {
try {
final permission = await Geolocator.checkPermission();
if (permission == LocationPermission.denied ||
permission == LocationPermission.deniedForever) {
final requested = await Geolocator.requestPermission();
if (requested == LocationPermission.denied) {
return (LocationStatus.deniedTemporarily, null);
}
if (requested == LocationPermission.deniedForever) {
return (LocationStatus.deniedForever, null);
}
}
final position = await Geolocator.getCurrentPosition(
locationSettings: const LocationSettings(
accuracy: LocationAccuracy.high,
distanceFilter: 100,
),
);
return (LocationStatus.success, position);
} catch (e) {
return (LocationStatus.error, null);
}
}
2. Firebase Configuration Files Missing in GitHub Actions
Problem
CI pipeline required firebase_options.dart
file to build the app, but this file contains sensitive data and canβt be committed to version control. This caused build failures with missing file errors.
Root cause analysis
GitHub Secrets handle multiline and special characters inconsistently
- name: Decode firebase_options.dart
run: |
mkdir -p lib
echo "$" | base64 --decode > lib/firebase_options.dart
3. Profile Update Data Synchronization
Problem
When users updated their profiles, changes needed to propagate to all their existing posts and comments. Client-side processing risked partial failures due to network errors or app crashes, potentially causing data inconsistency.
collectionGroup
queries to efficiently query and update comments across all posts