Skip to content

Commit 2b83519

Browse files
committed
Add README.md to each example and life cycle observer
1 parent 5dbb32a commit 2b83519

File tree

9 files changed

+695
-8
lines changed

9 files changed

+695
-8
lines changed

LIFECYCLE.md

Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
# OpenMapView Lifecycle Management
2+
3+
This document explains how OpenMapView handles Android lifecycle events and compares it with Google's MapView approach.
4+
5+
## Why Lifecycle Management Matters
6+
7+
Map views need to manage resources properly:
8+
- **Network connections** for downloading tiles
9+
- **Memory caches** for storing bitmaps
10+
- **Coroutines** for async operations
11+
- **HTTP clients** for tile requests
12+
13+
Without proper lifecycle management, your app will:
14+
- Waste battery downloading tiles when in background
15+
- Leak memory by not cleaning up caches
16+
- Keep network connections open unnecessarily
17+
18+
## How Google MapView Does It
19+
20+
Google provides two approaches:
21+
22+
### Approach 1: MapView (XML-based) - Manual Lifecycle
23+
24+
```kotlin
25+
class MainActivity : AppCompatActivity() {
26+
private lateinit var mapView: MapView
27+
28+
override fun onCreate(savedInstanceState: Bundle?) {
29+
super.onCreate(savedInstanceState)
30+
mapView = findViewById(R.id.mapView)
31+
mapView.onCreate(savedInstanceState) // REQUIRED
32+
}
33+
34+
override fun onResume() {
35+
super.onResume()
36+
mapView.onResume() // REQUIRED
37+
}
38+
39+
override fun onPause() {
40+
super.onPause()
41+
mapView.onPause() // REQUIRED
42+
}
43+
44+
override fun onDestroy() {
45+
super.onDestroy()
46+
mapView.onDestroy() // REQUIRED
47+
}
48+
49+
override fun onSaveInstanceState(outState: Bundle) {
50+
super.onSaveInstanceState(outState)
51+
mapView.onSaveInstanceState(outState) // REQUIRED
52+
}
53+
54+
override fun onLowMemory() {
55+
super.onLowMemory()
56+
mapView.onLowMemory() // REQUIRED
57+
}
58+
}
59+
```
60+
61+
**Pros:** Fine-grained control
62+
**Cons:** Easy to forget, verbose, error-prone
63+
64+
### Approach 2: SupportMapFragment - Automatic Lifecycle
65+
66+
```kotlin
67+
class MainActivity : AppCompatActivity() {
68+
override fun onCreate(savedInstanceState: Bundle?) {
69+
super.onCreate(savedInstanceState)
70+
71+
val mapFragment = SupportMapFragment.newInstance()
72+
supportFragmentManager.beginTransaction()
73+
.add(R.id.container, mapFragment)
74+
.commit()
75+
}
76+
// No lifecycle methods needed!
77+
}
78+
```
79+
80+
**Pros:** Automatic, no boilerplate
81+
**Cons:** Must use Fragment architecture
82+
83+
## How OpenMapView Does It
84+
85+
OpenMapView uses **Android Architecture Components** - specifically `DefaultLifecycleObserver` - which provides the best of both worlds.
86+
87+
### The Implementation
88+
89+
OpenMapView implements `DefaultLifecycleObserver`:
90+
91+
```kotlin
92+
class OpenMapView(context: Context) :
93+
FrameLayout(context),
94+
DefaultLifecycleObserver { // Implements lifecycle callbacks
95+
96+
override fun onResume(owner: LifecycleOwner) {
97+
// Called when app comes to foreground
98+
}
99+
100+
override fun onPause(owner: LifecycleOwner) {
101+
// Called when app goes to background
102+
}
103+
104+
override fun onDestroy(owner: LifecycleOwner) {
105+
// Clean up resources
106+
}
107+
}
108+
```
109+
110+
### Usage Pattern
111+
112+
You just need to register the observer once:
113+
114+
```kotlin
115+
@Composable
116+
fun MapViewScreen() {
117+
val lifecycleOwner = androidx.lifecycle.compose.LocalLifecycleOwner.current
118+
119+
AndroidView(
120+
factory = { context ->
121+
OpenMapView(context).apply {
122+
// Register lifecycle observer - ONE LINE!
123+
lifecycleOwner.lifecycle.addObserver(this)
124+
125+
setCenter(LatLng(51.4661, 7.2491))
126+
setZoom(14.0)
127+
}
128+
}
129+
)
130+
}
131+
```
132+
133+
**That's it!** The lifecycle is automatically managed from this point forward.
134+
135+
## What Happens During Each Lifecycle Event
136+
137+
### onResume()
138+
```kotlin
139+
fun onResume() {
140+
// Called when app comes to foreground
141+
// Could be used to resume tile downloads if paused
142+
}
143+
```
144+
145+
**Current implementation:** Does nothing (tiles continue downloading)
146+
**Future optimization:** Could resume paused downloads
147+
148+
### onPause()
149+
```kotlin
150+
fun onPause() {
151+
// Called when app goes to background
152+
// Could be used to pause tile downloads to save battery
153+
}
154+
```
155+
156+
**Current implementation:** Does nothing (tiles continue downloading)
157+
**Future optimization:** Could pause ongoing downloads to save battery
158+
159+
### onDestroy()
160+
```kotlin
161+
fun onDestroy() {
162+
// Clean up resources to prevent memory leaks
163+
scope.cancel() // Cancel all coroutines (tile downloads)
164+
tileDownloader.close() // Close HTTP client
165+
tileCache.clear() // Clear cached bitmaps
166+
MarkerIconFactory.clearCache() // Clear default marker icon
167+
}
168+
```
169+
170+
**Current implementation:** Full cleanup!
171+
- Cancels all running tile downloads
172+
- Closes Ktor HTTP client
173+
- Clears bitmap memory cache
174+
- Releases marker icon bitmap
175+
176+
## Best Practices
177+
178+
### DO: Register the lifecycle observer
179+
180+
```kotlin
181+
// GOOD - Proper cleanup will happen
182+
OpenMapView(context).apply {
183+
lifecycleOwner.lifecycle.addObserver(this)
184+
// ... configure map
185+
}
186+
```
187+
188+
### DON'T: Forget to register
189+
190+
```kotlin
191+
// BAD - Memory leaks on Activity destruction
192+
OpenMapView(context).apply {
193+
// Missing: lifecycleOwner.lifecycle.addObserver(this)
194+
setCenter(...) // Map works but won't clean up!
195+
}
196+
```
197+
198+
## Comparison Table
199+
200+
| Feature | Google MapView | Google SupportMapFragment | OpenMapView |
201+
|---------|---------------|---------------------------|-------------|
202+
| **Lifecycle Setup** | Manual (6 methods) | Automatic | Semi-automatic (1 line) |
203+
| **Memory Leaks if Forgotten** | Yes | No | Yes (but less likely) |
204+
| **Boilerplate Code** | High | Low | Very Low |
205+
| **Flexibility** | High | Medium | High |
206+
| **Works with Compose** | No | No | Yes |
207+
| **Works with XML** | Yes | Yes | Yes |
208+
209+
## Testing Lifecycle
210+
211+
To verify lifecycle is working:
212+
213+
1. Run the app and observe Logcat
214+
2. Press home button - `onPause()` should be called
215+
3. Return to app - `onResume()` should be called
216+
4. Kill the app - `onDestroy()` should be called
217+
218+
Look for these log messages (if you add logging):
219+
```
220+
OpenMapView: onResume called
221+
OpenMapView: onPause called
222+
OpenMapView: onDestroy called - cleaning up resources
223+
```
224+
225+
## What If I Forget?
226+
227+
If you forget to register the lifecycle observer:
228+
229+
**What works:**
230+
- Map displays correctly
231+
- Panning and zooming work
232+
- Markers work
233+
- Everything appears normal
234+
235+
**What breaks:**
236+
- Memory leaks when Activity is destroyed
237+
- Tile downloads continue in background after app closes
238+
- HTTP client stays open
239+
- Cached bitmaps not released
240+
241+
**How to detect:**
242+
- Run app, use map, then close app
243+
- Check Android Profiler in Android Studio
244+
- Look for memory not being released
245+
- Check for ongoing network activity after app closes
246+
247+
## Summary
248+
249+
OpenMapView uses modern Android Architecture Components to provide:
250+
- Simple one-line lifecycle registration
251+
- Automatic cleanup when Activity/Fragment is destroyed
252+
- Prevention of memory leaks and battery drain
253+
- Best practices demonstrated in all example apps
254+
255+
Always register the lifecycle observer in production apps!

README.md

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,82 @@ A modern, Kotlin-first MapView replacement for Android — powered by [OpenStree
1313
- Extensible marker, overlay, and gesture handling
1414
- MIT licensed (use freely in commercial apps)
1515

16+
## Examples
17+
18+
Explore the example applications to see OpenMapView in action:
19+
20+
### [Example01Pan](examples/Example01Pan) - Basic Map Panning
21+
22+
![Example01Pan](examples/Example01Pan/screenshot.gif)
23+
24+
Demonstrates basic map tile rendering and touch pan gestures.
25+
26+
**Features:** Map tiles, touch panning, smooth updates
27+
28+
[View Details](examples/Example01Pan/README.md)
29+
30+
### [Example02Zoom](examples/Example02Zoom) - Zoom Controls and Gestures
31+
32+
![Example02Zoom](examples/Example02Zoom/screenshot.gif)
33+
34+
Shows zoom functionality with FAB controls and pinch-to-zoom gestures.
35+
36+
**Features:** Programmatic zoom, pinch gestures, zoom level display, zoom limits
37+
38+
[View Details](examples/Example02Zoom/README.md)
39+
40+
### [Example03Markers](examples/Example03Markers) - Marker Overlays
41+
42+
![Example03Markers](examples/Example03Markers/screenshot.gif)
43+
44+
Demonstrates marker system with custom icons and click handling.
45+
46+
**Features:** Multiple markers, click detection, toast notifications, custom icons support
47+
48+
[View Details](examples/Example03Markers/README.md)
49+
1650
## Getting Started
1751

52+
### With Jetpack Compose
53+
54+
```kotlin
55+
@Composable
56+
fun MapViewScreen() {
57+
val lifecycleOwner = androidx.lifecycle.compose.LocalLifecycleOwner.current
58+
59+
AndroidView(
60+
factory = { context ->
61+
OpenMapView(context).apply {
62+
// Register lifecycle observer for proper cleanup
63+
lifecycleOwner.lifecycle.addObserver(this)
64+
65+
setCenter(LatLng(51.4661, 7.2491)) // Bochum, Germany
66+
setZoom(14.0)
67+
68+
// Add markers (optional)
69+
addMarker(Marker(
70+
position = LatLng(51.4661, 7.2491),
71+
title = "Bochum City Center"
72+
))
73+
}
74+
},
75+
modifier = Modifier.fillMaxSize()
76+
)
77+
}
78+
```
79+
80+
### With XML Layouts
81+
1882
```kotlin
1983
class MainActivity : AppCompatActivity() {
2084
override fun onCreate(savedInstanceState: Bundle?) {
2185
super.onCreate(savedInstanceState)
2286
setContentView(R.layout.activity_main)
2387

2488
val mapView = findViewById<OpenMapView>(R.id.mapView)
25-
mapView.setTileSource(TileSource.STANDARD)
2689
mapView.setZoom(14.0)
27-
mapView.setCenter(LatLng(51.4661, 7.2491)) // Bochum, Germany
90+
mapView.setCenter(LatLng(51.4661, 7.2491))
2891
}
2992
}
93+
```
3094

0 commit comments

Comments
 (0)