Skip to content

Commit 1f22fb6

Browse files
shubham7109TADraesekealan-edihud1083701smito01
authored
Release v200.7.0 (#340)
Co-authored-by: Trevor Draeseke <[email protected]> Co-authored-by: TADraeseke <[email protected]> Co-authored-by: Alan Lucas <[email protected]> Co-authored-by: hud10837 <[email protected]> Co-authored-by: Oliver Smith <[email protected]> Co-authored-by: Darryl Lynch <[email protected]> Co-authored-by: Erick Lopez Solis <[email protected]> Co-authored-by: Shelly Gill <[email protected]>
1 parent f429386 commit 1f22fb6

File tree

467 files changed

+20836
-5069
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

467 files changed

+20836
-5069
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ build/
1515
*.classpath
1616
.settings/
1717
.vscode
18+
*.salive
1819

1920
# OS generated files #
2021
.DS_Store

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
## Overview
1313

14-
ArcGIS Maps SDK for Kotlin v200.6.0 samples. The `main` branch of this repository contains sample app modules for the latest available version of the [ArcGIS Maps Kotlin SDK](https://developers.arcgis.com/kotlin/). Samples released under older versions can be found through the [git tags](https://github.com/Esri/arcgis-maps-sdk-kotlin-samples/tags). Please read our [wiki](https://github.com/Esri/arcgis-maps-sdk-kotlin-samples/wiki) for help with working with this repository.
14+
ArcGIS Maps SDK for Kotlin v200.7.0 samples. The `main` branch of this repository contains sample app modules for the latest available version of the [ArcGIS Maps Kotlin SDK](https://developers.arcgis.com/kotlin/). Samples released under older versions can be found through the [git tags](https://github.com/Esri/arcgis-maps-sdk-kotlin-samples/tags). Please read our [wiki](https://github.com/Esri/arcgis-maps-sdk-kotlin-samples/wiki) for help with working with this repository.
1515

1616
## Prerequisites
1717

app/build.gradle.kts

+4-4
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,10 @@ plugins {
77
alias(libs.plugins.kotlin.serialization)
88
alias(libs.plugins.gradle.secrets)
99
alias(libs.plugins.sample.files.copy)
10-
alias(libs.plugins.screenshots.copy)
1110
alias(libs.plugins.ksp)
1211
}
1312

1413
tasks.named("preBuild").configure { dependsOn("copyCodeFiles") }
15-
tasks.named("preBuild").configure { dependsOn("copyScreenshots") }
1614

1715
secrets {
1816
// this file doesn't contain secrets, it just provides defaults which can be committed into git.
@@ -28,7 +26,7 @@ android {
2826
}
2927

3028
// Optional input to apply the external signing configuration for the sample viewer
31-
// Example: ./gradlew assembleRelease -PsigningPropsFilePath=absolute-file-path/signing.properties -D build=200.6.0
29+
// Example: ./gradlew assembleRelease -PsigningPropsFilePath=absolute-file-path/signing.properties -D build=200.7.0
3230
val signingPropsFilePath = project.findProperty("signingPropsFilePath").toString()
3331
val signingPropsFile = rootProject.file(signingPropsFilePath)
3432

@@ -71,7 +69,9 @@ android {
7169

7270
dependencies {
7371
for (sampleFile in file("../samples").listFiles()!!) {
74-
if (sampleFile.isDirectory) {
72+
if (sampleFile.isDirectory &&
73+
sampleFile.listFiles()?.find { it.name.equals("build.gradle.kts") } != null
74+
) {
7575
implementation(project(":samples:" + sampleFile.name))
7676
}
7777
}

app/src/main/AndroidManifest.xml

+5-1
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,18 @@
1616
android:theme="@style/AppTheme"
1717
tools:targetApi="31">
1818
<activity
19-
android:name=".MainActivity"
19+
android:name=".SampleViewerLauncherActivity"
2020
android:exported="true">
2121
<intent-filter>
2222
<action android:name="android.intent.action.MAIN" />
2323

2424
<category android:name="android.intent.category.LAUNCHER" />
2525
</intent-filter>
2626
</activity>
27+
<activity
28+
android:name=".MainActivity"
29+
android:exported="true">
30+
</activity>
2731
</application>
2832

2933
</manifest>

app/src/main/java/com/esri/arcgismaps/kotlin/sampleviewer/MainActivity.kt

+31-6
Original file line numberDiff line numberDiff line change
@@ -22,22 +22,47 @@ import androidx.activity.compose.setContent
2222
import androidx.activity.enableEdgeToEdge
2323
import androidx.compose.material3.MaterialTheme
2424
import androidx.compose.material3.Surface
25+
import androidx.compose.runtime.Composable
26+
import androidx.compose.runtime.getValue
27+
import androidx.compose.runtime.mutableStateOf
28+
import androidx.compose.runtime.remember
29+
import androidx.compose.runtime.setValue
2530
import com.esri.arcgismaps.kotlin.sampleviewer.navigation.NavGraph
31+
import com.esri.arcgismaps.sample.sampleslib.components.MessageDialog
2632
import com.esri.arcgismaps.sample.sampleslib.theme.SampleAppTheme
2733

2834
class MainActivity : ComponentActivity() {
2935
override fun onCreate(savedInstanceState: Bundle?) {
3036
super.onCreate(savedInstanceState)
3137
enableEdgeToEdge()
3238
setContent {
33-
setContent {
34-
35-
SampleAppTheme {
36-
Surface(color = MaterialTheme.colorScheme.background) {
37-
NavGraph()
38-
}
39+
SampleAppTheme {
40+
Surface(color = MaterialTheme.colorScheme.background) {
41+
NavGraph()
42+
HandleExceptions()
3943
}
4044
}
4145
}
4246
}
47+
48+
/**
49+
* Responsible for managing incoming exceptions and displaying it as a dialog in the UI.
50+
*/
51+
@Composable
52+
fun HandleExceptions() {
53+
var sampleViewerException by remember {
54+
mutableStateOf(
55+
intent.extras?.let {
56+
it.getStringArray("SampleViewerException") ?: arrayOf()
57+
} ?: arrayOf<String>()
58+
)
59+
}
60+
if (sampleViewerException.isNotEmpty()) {
61+
MessageDialog(
62+
onDismissRequest = { sampleViewerException = arrayOf() },
63+
title = sampleViewerException[0],
64+
description = sampleViewerException[1]
65+
)
66+
}
67+
}
4368
}

app/src/main/java/com/esri/arcgismaps/kotlin/sampleviewer/SampleViewerApplication.kt

+16
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,19 @@
1+
/* Copyright 2024 Esri
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*
15+
*/
16+
117
package com.esri.arcgismaps.kotlin.sampleviewer
218

319
import android.app.Application
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/* Copyright 2024 Esri
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*
15+
*/
16+
17+
package com.esri.arcgismaps.kotlin.sampleviewer
18+
19+
import android.content.Intent
20+
import android.os.Bundle
21+
import android.os.Handler
22+
import android.os.Looper
23+
import android.util.Log
24+
import androidx.activity.ComponentActivity
25+
26+
class SampleViewerLauncherActivity : ComponentActivity(), ExceptionListener {
27+
override fun onCreate(savedInstanceState: Bundle?) {
28+
super.onCreate(savedInstanceState)
29+
setupExceptionHandler()
30+
startMainActivity(null)
31+
}
32+
33+
/**
34+
* Prepares extras for the intent with error information if a [throwable] is provided.
35+
* It then starts the [MainActivity] by creating an intent with necessary flag and extras.
36+
*/
37+
private fun startMainActivity(throwable: Throwable?) {
38+
val extras = Bundle()
39+
if (throwable != null) {
40+
extras.putStringArray(
41+
/* key = */ "SampleViewerException",
42+
/* value = */ arrayOf(throwable.message.toString(), throwable.cause.toString())
43+
)
44+
}
45+
46+
startActivity(Intent(this, MainActivity::class.java).apply {
47+
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
48+
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
49+
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
50+
putExtras(extras)
51+
})
52+
}
53+
54+
/**
55+
* Overrides [ExceptionListener] to capture the [throwable]
56+
* and starts the [MainActivity] with the exception details.
57+
*/
58+
override fun uncaughtException(throwable: Throwable) {
59+
Log.e("SampleViewerException", throwable.message.toString(), throwable)
60+
startMainActivity(throwable)
61+
}
62+
63+
/**
64+
* Posts a task to the main [Looper] to handle uncaught exceptions.
65+
*/
66+
private fun setupExceptionHandler() {
67+
Handler(Looper.getMainLooper()).post {
68+
while (true) {
69+
try {
70+
Looper.loop()
71+
} catch (e: Throwable) {
72+
uncaughtException(e)
73+
}
74+
}
75+
}
76+
Thread.setDefaultUncaughtExceptionHandler { _, e ->
77+
uncaughtException(e)
78+
}
79+
}
80+
}
81+
82+
/**
83+
* This interface defines a method [uncaughtException] to handle uncaught exceptions.
84+
*/
85+
interface ExceptionListener {
86+
fun uncaughtException(throwable: Throwable)
87+
}

app/src/main/java/com/esri/arcgismaps/kotlin/sampleviewer/model/DefaultSampleInfoRepository.kt

+54-45
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,22 @@ package com.esri.arcgismaps.kotlin.sampleviewer.model
1919
import android.content.Context
2020
import android.util.Log
2121
import com.esri.arcgismaps.kotlin.sampleviewer.model.Sample.Companion.loadActivityPath
22+
import com.esri.arcgismaps.kotlin.sampleviewer.model.Sample.Companion.loadCodeFiles
2223
import com.esri.arcgismaps.kotlin.sampleviewer.model.Sample.Companion.loadReadMe
2324
import com.esri.arcgismaps.kotlin.sampleviewer.model.Sample.Companion.loadScreenshot
2425
import com.esri.arcgismaps.kotlin.sampleviewer.model.room.AppDatabase
2526
import com.esri.arcgismaps.kotlin.sampleviewer.model.room.Converters
2627
import kotlinx.coroutines.Dispatchers
28+
import kotlinx.coroutines.flow.Flow
29+
import kotlinx.coroutines.flow.MutableStateFlow
30+
import kotlinx.coroutines.flow.asStateFlow
31+
import kotlinx.coroutines.flow.map
2732
import kotlinx.coroutines.withContext
2833
import kotlinx.serialization.json.Json
2934
import java.util.concurrent.atomic.AtomicBoolean
3035

3136
/**
32-
* The single source of truth for app wide data. It reads the sample metadata to create as list of
37+
* The single source of truth for app wide data. It reads the sample metadata to create a list of
3338
* [Sample] objects and populates the database used for search.
3439
* It also provides functions to get samples by category, name, or all samples.
3540
*/
@@ -39,55 +44,59 @@ object DefaultSampleInfoRepository : SampleInfoRepository {
3944

4045
private val json = Json { ignoreUnknownKeys = true }
4146

42-
private val sampleList = mutableListOf<Sample>()
47+
private val _sampleData = MutableStateFlow<List<Sample>>(emptyList())
48+
private val sampleData = _sampleData.asStateFlow()
4349

4450
/**
45-
* Load the sample metadata from the metadata folder in the assets directory and updates sampleList
51+
* Load the sample metadata from 'samples.json' in the assets directory and updates sampleList
4652
* of [Sample] objects.
4753
*/
4854
suspend fun load(context: Context) {
4955
if (isInitialized.compareAndSet(false, true)) {
50-
// Iterate through the metadata folder for all metadata files
56+
// List that will be populated with samples
57+
val sampleList = mutableListOf<Sample>()
5158

52-
context.assets.list("samples")?.forEach { samplePath ->
53-
// Get this metadata files as a string
54-
context.assets.open("samples/$samplePath/README.metadata.json").use { inputStream ->
55-
val metadataJsonString = inputStream.bufferedReader().use { it.readText() }
56-
try {
57-
val metadata = json.decodeFromString<SampleMetadata>(metadataJsonString)
59+
// Read the entire 'samples.json' from build/sampleAssets/samples/
60+
val samplesJsonString = context.assets.open("samples/samples.json").use { stream ->
61+
stream.bufferedReader().use { it.readText() }
62+
}
63+
64+
// Parse it into a map of: sampleFolderName -> mapOf( filename -> fileContent )
65+
val allSamplesData = json.decodeFromString<Map<String, Map<String, String>>>(samplesJsonString)
66+
67+
// Build each Sample using the metadata from "README.metadata.json"
68+
allSamplesData.forEach { (sampleFolderName, fileMap) ->
69+
val metadataJsonString = fileMap["README.metadata.json"]
70+
?: throw Exception("README.metadata.json not found in sample: $sampleFolderName")
71+
try {
72+
val metadata = json.decodeFromString<SampleMetadata>(metadataJsonString)
5873

59-
// Create and add a new sample metadata data class object to the list
60-
val sample = Sample(
61-
name = metadata.title,
62-
codeFiles = Sample.loadCodeFiles(
63-
context = context,
64-
sampleName = metadata.title
65-
),
66-
url = "https://developers.arcgis.com/kotlin/sample-code/" +
67-
metadata.title.replace(" ", "-").lowercase(),
68-
readMe = loadReadMe(
69-
context = context,
70-
sampleName = samplePath
71-
),
72-
screenshotURL = loadScreenshot(
73-
sampleName = metadata.title,
74-
imageArray = metadata.imagePaths
75-
),
76-
mainActivity = loadActivityPath(
77-
codePaths = metadata.codePaths
78-
),
79-
metadata = metadata,
80-
)
81-
// Add the new sample to the list
82-
sampleList.add(sample)
83-
} catch (e: Exception) {
84-
Log.e(
85-
DefaultSampleInfoRepository::class.simpleName,
86-
"Exception at $samplePath: " + e.printStackTrace()
87-
)
88-
}
74+
// Create and add a new sample metadata data class object to the list
75+
val sample = Sample(
76+
name = metadata.title,
77+
codeFiles = loadCodeFiles(fileMap),
78+
url = "https://developers.arcgis.com/kotlin/sample-code/" +
79+
metadata.title.replace(" ", "-").lowercase(),
80+
readMe = loadReadMe(fileMap),
81+
screenshotURL = loadScreenshot(
82+
sampleName = metadata.title,
83+
imageArray = metadata.imagePaths
84+
),
85+
mainActivity = loadActivityPath(
86+
codePaths = metadata.codePaths
87+
),
88+
metadata = metadata
89+
)
90+
// Add the new sample to the list
91+
sampleList.add(sample)
92+
} catch (e: Exception) {
93+
Log.e(
94+
DefaultSampleInfoRepository::class.simpleName,
95+
"Exception at $sampleFolderName: ${e.stackTraceToString()}"
96+
)
8997
}
9098
}
99+
_sampleData.value = sampleList
91100
withContext(Dispatchers.IO) {
92101
// Populates the Room SQL database
93102
populateDatabase(context)
@@ -99,22 +108,22 @@ object DefaultSampleInfoRepository : SampleInfoRepository {
99108
* Populates the Room SQL database with all samples and sample info
100109
*/
101110
private suspend fun populateDatabase(context: Context) {
102-
val sampleEntities = sampleList.map { Converters().convertToEntity(sample = it) }
111+
val sampleEntities = sampleData.value.map { Converters().convertToEntity(sample = it) }
103112
AppDatabase.getDatabase(context).clearAllTables()
104113
AppDatabase.getDatabase(context).sampleDao().insertAll(samples = sampleEntities)
105114
}
106115

107116
/**
108117
* Get a sample by its name. Either the formal name or title.
109118
*/
110-
override fun getSampleByName(sampleName: String): Sample {
111-
return sampleList.first { it.metadata.formalName == sampleName || it.metadata.title == sampleName }
119+
override fun getSampleByName(sampleName: String): Flow<Sample> {
120+
return sampleData.map { it.first { sample -> sample.metadata.formalName == sampleName || sample.metadata.title == sampleName } }
112121
}
113122

114123
/**
115124
* Get a list of samples for the given [SampleCategory].
116125
*/
117-
override fun getSamplesInCategory(sampleCategory: SampleCategory): List<Sample> {
118-
return sampleList.filter { it.metadata.sampleCategory == sampleCategory }
126+
override fun getSamplesInCategory(sampleCategory: SampleCategory): Flow<List<Sample>> {
127+
return sampleData.map { it.filter { sample -> sample.metadata.sampleCategory == sampleCategory } }
119128
}
120129
}

0 commit comments

Comments
 (0)