π App Introduction: Mobile app for finding nearby places using Naver Local Search API and GPS location services
π Duration: April 20, 2025 ~ April 22, 2025 (3 days)
π± Platform: Flutter cross-platform app (iOS, Android)
π₯ Team Size: 1 developer (Personal project)
πΌ Role: Entire app UI/UX design & development, API integration
π οΈ Key Technologies: Flutter
Dart
Naver Local API
VWorld API
Riverpod
Geolocator
InAppWebView
URL Launcher
Dio
π GitHub: daehan-lim/flutter-place-finder
PlaceFinder helps users discover nearby places by searching by place names or addresses. The app uses Naver Local Search API and VWorld API to provide GPS-based location search and supports seamless redirection to popular map applications. Users can quickly explore local businesses, restaurants, and points of interest while accessing detailed information through in-app web view integration.
βββ app/ # App configuration and setup files
β βββ constants/ # App-wide constant definitions
β β βββ app_colors.dart # Color constants
β β βββ app_constants.dart # General app constants
β β βββ app_styles.dart # Style definitions
β βββ app_providers.dart # Riverpod provider setup
β βββ theme.dart # App theme configuration
β
βββ core/ # Core functionality and common utilities
β βββ exceptions/ # App-wide exception classes
β β βββ data_exceptions.dart # Data-related exception classes
β βββ services/ # External service integrations
β β βββ map_launcher_service.dart
β βββ utils/ # Helper functions and utility classes
β βββ geolocator_util.dart
β βββ snackbar_util.dart
β βββ string_format_utils.dart
β
βββ data/ # Data layer and data access
β βββ dto/ # Data Transfer Objects
β β βββ naver_place_dto.dart
β β βββ vworld_district_dto.dart
β βββ model/ # Data models
β β βββ place.dart
β βββ network/ # Network communication
β β βββ dio_clients.dart
β βββ repository/ # Repository implementations
β βββ location_repository.dart
β
βββ ui/ # User interface
β βββ pages/ # App screens
β β βββ home/ # Home screen
β β β βββ home_page.dart
β β β βββ home_view_model.dart
β β β βββ widgets/
β β β βββ home_list_item.dart
β β βββ web/ # WebView screen
β β βββ place_web_page.dart
β β βββ place_web_page_view_model.dart
β βββ widgets/ # Common widgets
β βββ error_layout.dart
β
βββ main.dart # App entry point
Geolocator
for GPS coordinate acquisition with VWorld API
to automatically identify userβs current administrative districtBearer Token
authentication and Dio
HTTP clientBaseOptions
for consistent HTTP settings with 10-second connection/reception timeout and debug logging through LogInterceptor
Geo URI
scheme allowing users to choose from installed map apps (Google Maps, Naver Map, KakaoMap)URL Launcher
integration for smooth transitions to map applicationsCustom User Agent
configurationInAppBrowserView
mode for Naver search integrationApiException
, NetworkException
, EnvFileException
for specific scenariosTooltip
for long addressesInkWell
effects and shadowsGestureDetector
for better user convenienceProvider
pattern and dependency injectionAsyncValue
DTO
and Model
flutter_dotenv
.env.example
file to guide development environment configurationHomeListItem
, MessageLayout
StringFormatUtils
, SnackbarUtil
iOS Map App Integration Silent Failure
Problem
On iOS, when Naver Map is not installed and launchUrl()
is called with the nmap://
custom scheme, it fails silently without any response. The Apple Maps
fallback configured with try/catch
never executes, leaving users with no feedback or alternative action.
canLaunchUrl()
in the Android implementation. It would return false
even for executable geo:
URIs:if (await canLaunchUrl(Uri.parse('geo:0,0?q=$encoded'))) {
// Would return false despite being executable
}
canLaunchUrl()
would be equally unreliable on iOS, so I used try/catch
for failure handling on both platforms
static Future<void> openInMap(String queryAddress) async {
if (Platform.isIOS) {
try {
await launchUrl(naverUri, mode: LaunchMode.externalApplication);
} catch (e) {
// This catch block never executes on iOS
final appleUri = Uri.parse('http://maps.apple.com/?q=$encoded');
await launchUrl(appleUri, mode: LaunchMode.externalApplication);
}
}
}
The approach failed because iOS launchUrl()
doesnβt throw exceptions for unavailable apps - it just silently does nothing, so the catch block never executes and Apple Maps never launches.
canLaunchUrl()
geo:
Final Solution
Implemented platform-specific handling that works with each OSβs characteristics:
static Future<void> openInMap(String queryAddress) async {
if (Platform.isIOS) {
final naverUri = Uri.parse('nmap://search?query=$encoded&appname=$appName');
if (await canLaunchUrl(naverUri)) {
await launchUrl(naverUri, mode: LaunchMode.externalApplication);
} else {
// Naver Map unavailable, use Apple Maps
final appleUri = Uri.parse('http://maps.apple.com/?q=$encoded');
if (await canLaunchUrl(appleUri)) {
await launchUrl(appleUri, mode: LaunchMode.externalApplication);
}
}
} else {
// Android: Keep try/catch approach for geo URIs
try {
await launchUrl(geoUri, mode: LaunchMode.externalApplication);
} catch (e) {
log('Could not launch map: $e');
}
}
}