From 7e1dcad9d95056c5f836706d218612316e21bdca Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Dec 2023 10:59:36 +0100 Subject: [PATCH 01/28] Bump the github-actions group with 1 update (#250) Bumps the github-actions group with 1 update: [actions/upload-artifact](https://github.com/actions/upload-artifact). - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 63a4fa59..1058dd81 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,7 +37,7 @@ jobs: run: ./tests.sh - name: Store test results if: success() || failure() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: test-results path: | From 0d78519dab732e1ab6cd0e5780fc0e56810283a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9sar?= <56847527+LikeTheSalad@users.noreply.github.com> Date: Tue, 19 Dec 2023 10:49:45 +0100 Subject: [PATCH 02/28] Add sample app (#249) * Created sample app dir * Adding code from sample-app-android-apm * Setting latest lib version on sample app * Updating the sample app README * Adding doc comment --- sample-app/README.md | 59 +++++ sample-app/app/build.gradle | 59 +++++ sample-app/app/src/main/AndroidManifest.xml | 36 +++ .../apm/android/sample/FirstFragment.kt | 48 ++++ .../apm/android/sample/MainActivity.kt | 38 +++ .../co/elastic/apm/android/sample/MyApp.kt | 12 + .../apm/android/sample/SecondFragment.kt | 70 ++++++ .../sample/network/CityWeatherService.kt | 12 + .../sample/network/WeatherRestManager.kt | 20 ++ .../network/data/CurrentWeatherResponse.kt | 3 + .../sample/network/data/ForecastResponse.kt | 8 + .../drawable-v24/ic_launcher_foreground.xml | 30 +++ .../res/drawable/ic_launcher_background.xml | 170 +++++++++++++ .../app/src/main/res/layout/activity_main.xml | 34 +++ .../app/src/main/res/layout/content_main.xml | 19 ++ .../src/main/res/layout/fragment_first.xml | 43 ++++ .../src/main/res/layout/fragment_second.xml | 49 ++++ .../app/src/main/res/menu/menu_main.xml | 10 + .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 + .../src/main/res/mipmap-hdpi/ic_launcher.webp | Bin 0 -> 1404 bytes .../res/mipmap-hdpi/ic_launcher_round.webp | Bin 0 -> 2898 bytes .../src/main/res/mipmap-mdpi/ic_launcher.webp | Bin 0 -> 982 bytes .../res/mipmap-mdpi/ic_launcher_round.webp | Bin 0 -> 1772 bytes .../main/res/mipmap-xhdpi/ic_launcher.webp | Bin 0 -> 1900 bytes .../res/mipmap-xhdpi/ic_launcher_round.webp | Bin 0 -> 3918 bytes .../main/res/mipmap-xxhdpi/ic_launcher.webp | Bin 0 -> 2884 bytes .../res/mipmap-xxhdpi/ic_launcher_round.webp | Bin 0 -> 5914 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.webp | Bin 0 -> 3844 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.webp | Bin 0 -> 7778 bytes .../app/src/main/res/navigation/nav_graph.xml | 32 +++ .../app/src/main/res/values-land/dimens.xml | 3 + .../app/src/main/res/values-night/themes.xml | 16 ++ .../src/main/res/values-w1240dp/dimens.xml | 3 + .../app/src/main/res/values-w600dp/dimens.xml | 3 + sample-app/app/src/main/res/values/colors.xml | 10 + sample-app/app/src/main/res/values/dimens.xml | 3 + .../app/src/main/res/values/strings.xml | 22 ++ sample-app/app/src/main/res/values/themes.xml | 25 ++ .../app/src/main/res/xml/backup_rules.xml | 13 + .../main/res/xml/data_extraction_rules.xml | 19 ++ sample-app/backend/build.gradle | 20 ++ .../sample/backend/BackendApplication.java | 15 ++ .../sample/backend/WeatherController.java | 37 +++ .../backend/data/CurrentWeatherResponse.java | 24 ++ .../sample/backend/data/ForecastResponse.java | 28 +++ .../android/sample/backend/data/Location.java | 34 +++ .../src/main/resources/elasticapm.properties | 4 + sample-app/build.gradle | 6 + sample-app/gradle.properties | 3 + sample-app/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 59821 bytes .../gradle/wrapper/gradle-wrapper.properties | 5 + sample-app/gradlew | 234 ++++++++++++++++++ sample-app/gradlew.bat | 89 +++++++ sample-app/settings.gradle | 19 ++ 55 files changed, 1397 insertions(+) create mode 100644 sample-app/README.md create mode 100644 sample-app/app/build.gradle create mode 100644 sample-app/app/src/main/AndroidManifest.xml create mode 100644 sample-app/app/src/main/java/co/elastic/apm/android/sample/FirstFragment.kt create mode 100644 sample-app/app/src/main/java/co/elastic/apm/android/sample/MainActivity.kt create mode 100644 sample-app/app/src/main/java/co/elastic/apm/android/sample/MyApp.kt create mode 100644 sample-app/app/src/main/java/co/elastic/apm/android/sample/SecondFragment.kt create mode 100644 sample-app/app/src/main/java/co/elastic/apm/android/sample/network/CityWeatherService.kt create mode 100644 sample-app/app/src/main/java/co/elastic/apm/android/sample/network/WeatherRestManager.kt create mode 100644 sample-app/app/src/main/java/co/elastic/apm/android/sample/network/data/CurrentWeatherResponse.kt create mode 100644 sample-app/app/src/main/java/co/elastic/apm/android/sample/network/data/ForecastResponse.kt create mode 100644 sample-app/app/src/main/res/drawable-v24/ic_launcher_foreground.xml create mode 100644 sample-app/app/src/main/res/drawable/ic_launcher_background.xml create mode 100644 sample-app/app/src/main/res/layout/activity_main.xml create mode 100644 sample-app/app/src/main/res/layout/content_main.xml create mode 100644 sample-app/app/src/main/res/layout/fragment_first.xml create mode 100644 sample-app/app/src/main/res/layout/fragment_second.xml create mode 100644 sample-app/app/src/main/res/menu/menu_main.xml create mode 100644 sample-app/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 sample-app/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 sample-app/app/src/main/res/mipmap-hdpi/ic_launcher.webp create mode 100644 sample-app/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp create mode 100644 sample-app/app/src/main/res/mipmap-mdpi/ic_launcher.webp create mode 100644 sample-app/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp create mode 100644 sample-app/app/src/main/res/mipmap-xhdpi/ic_launcher.webp create mode 100644 sample-app/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp create mode 100644 sample-app/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp create mode 100644 sample-app/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp create mode 100644 sample-app/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp create mode 100644 sample-app/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp create mode 100644 sample-app/app/src/main/res/navigation/nav_graph.xml create mode 100644 sample-app/app/src/main/res/values-land/dimens.xml create mode 100644 sample-app/app/src/main/res/values-night/themes.xml create mode 100644 sample-app/app/src/main/res/values-w1240dp/dimens.xml create mode 100644 sample-app/app/src/main/res/values-w600dp/dimens.xml create mode 100644 sample-app/app/src/main/res/values/colors.xml create mode 100644 sample-app/app/src/main/res/values/dimens.xml create mode 100644 sample-app/app/src/main/res/values/strings.xml create mode 100644 sample-app/app/src/main/res/values/themes.xml create mode 100644 sample-app/app/src/main/res/xml/backup_rules.xml create mode 100644 sample-app/app/src/main/res/xml/data_extraction_rules.xml create mode 100644 sample-app/backend/build.gradle create mode 100644 sample-app/backend/src/main/java/co/elastic/apm/android/sample/backend/BackendApplication.java create mode 100644 sample-app/backend/src/main/java/co/elastic/apm/android/sample/backend/WeatherController.java create mode 100644 sample-app/backend/src/main/java/co/elastic/apm/android/sample/backend/data/CurrentWeatherResponse.java create mode 100644 sample-app/backend/src/main/java/co/elastic/apm/android/sample/backend/data/ForecastResponse.java create mode 100644 sample-app/backend/src/main/java/co/elastic/apm/android/sample/backend/data/Location.java create mode 100644 sample-app/backend/src/main/resources/elasticapm.properties create mode 100644 sample-app/build.gradle create mode 100644 sample-app/gradle.properties create mode 100644 sample-app/gradle/wrapper/gradle-wrapper.jar create mode 100644 sample-app/gradle/wrapper/gradle-wrapper.properties create mode 100755 sample-app/gradlew create mode 100644 sample-app/gradlew.bat create mode 100644 sample-app/settings.gradle diff --git a/sample-app/README.md b/sample-app/README.md new file mode 100644 index 00000000..9567ff11 --- /dev/null +++ b/sample-app/README.md @@ -0,0 +1,59 @@ +# Sample Android application for the Elastic APM Agent + +> This is part of +> our [blog post](https://www.elastic.co/blog/monitoring-android-applications-elastic-apm) on +> Monitoring Android applications with Elastic APM. If you need more detailed information on the +> overall usage of the Elastic APM Agent, you should take a look at it. + +To showcase an end-to-end scenario including distributed tracing we'll instrument this sample +weather application that comprises two Android UI fragments and a simple local backend +service based on Spring Boot. + +The first Fragment will have a dropdown list with some city names and also a button that takes you +to the second one, where you’ll see the selected city’s current temperature. If you pick a +non-European city on the first screen, you’ll get an error from the (local) backend when you head to +the second screen. This is to demonstrate how network and backend errors are captured and correlated +in Elastic APM. + +## How to run + +### Launching the local backend service + +As part of our sample app, we’re going to launch a simple local backend service that will handle our +app’s HTTP requests. The backend service is instrumented with +the [Elastic APM Java agent](https://www.elastic.co/guide/en/apm/agent/java/current/index.html) to +collect +and send its own APM data over to Elastic APM, allowing it to correlate the mobile interactions with +the processing of the backend requests. + +In order to configure the local server, we need to set our Elastic APM endpoint and secret token ( +the same used for our Android app in the previous step) into the +backend/src/main/resources/elasticapm.properties file: + +```properties +service_name=weather-backend +application_packages=co.elastic.apm.android.sample +server_url=YOUR_ELASTIC_APM_URL +secret_token=YOUR_ELASTIC_APM_SECRET_TOKEN +``` + +After the backend configuration is done, we can proceed to start the server by running the following +command in a terminal located in the root directory of our sample project: `./gradlew bootRun` (or +`gradlew.bat bootRun` if you’re on Windows). Alternatively, you can start the backend service from +Android Studio. + +### Using the app + +Launch the sample app in an Android emulator (from Android Studio). Once everything is running, we +need to navigate around in the app to generate some load that we would like to observe in Elastic +APM. So, select a city, click Next and repeat it multiple times. Please, also make sure to select +New York at least once. You will see that the weather forecast won’t work for New York as the city. +Below, we will use Elastic APM to find out what’s going wrong when selecting New York. + +### Analyzing the data + +After launching the app and navigating through it, you should be able to start seeing telemetry data +coming into your configured Kibana instance. For a more detailed overview of what to see there, you +should take a look +at [this blog post](https://www.elastic.co/blog/monitoring-android-applications-elastic-apm) on +Monitoring Android applications with Elastic APM. \ No newline at end of file diff --git a/sample-app/app/build.gradle b/sample-app/app/build.gradle new file mode 100644 index 00000000..74247dad --- /dev/null +++ b/sample-app/app/build.gradle @@ -0,0 +1,59 @@ +plugins { + id 'com.android.application' + id 'org.jetbrains.kotlin.android' + id 'co.elastic.apm.android' version '0.13.0' +} + +android { + namespace 'co.elastic.apm.android.sample' + compileSdk 32 + + defaultConfig { + applicationId "co.elastic.apm.android.sample" + minSdk 26 + targetSdk 32 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = '1.8' + } + buildFeatures { + viewBinding true + } +} + +elasticApm { + serviceName = "weather-sample-app" + serverUrl = "http://10.0.2.2:8200" // Your Elastic APM server endpoint. +// secretToken = "my-apm-secret-token" // Uncomment and set it if this is your preferred auth method. +} + +dependencies { + def lifecycle_version = "2.4.0" + def retrofit_version = "2.9.0" + implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" + implementation 'androidx.core:core-ktx:1.7.0' + implementation 'androidx.appcompat:appcompat:1.5.1' + implementation 'com.google.android.material:material:1.7.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.4' + implementation 'androidx.navigation:navigation-fragment-ktx:2.4.1' + implementation 'androidx.navigation:navigation-ui-ktx:2.4.1' + implementation "com.squareup.retrofit2:retrofit:$retrofit_version" + implementation "com.squareup.retrofit2:converter-gson:$retrofit_version" + testImplementation 'junit:junit:4.13.2' +} \ No newline at end of file diff --git a/sample-app/app/src/main/AndroidManifest.xml b/sample-app/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..6ade646e --- /dev/null +++ b/sample-app/app/src/main/AndroidManifest.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/sample-app/app/src/main/java/co/elastic/apm/android/sample/FirstFragment.kt b/sample-app/app/src/main/java/co/elastic/apm/android/sample/FirstFragment.kt new file mode 100644 index 00000000..a9e30b97 --- /dev/null +++ b/sample-app/app/src/main/java/co/elastic/apm/android/sample/FirstFragment.kt @@ -0,0 +1,48 @@ +package co.elastic.apm.android.sample + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ArrayAdapter +import androidx.core.os.bundleOf +import androidx.fragment.app.Fragment +import androidx.navigation.fragment.findNavController +import co.elastic.apm.android.sample.databinding.FragmentFirstBinding + +class FirstFragment : Fragment() { + + private var _binding: FragmentFirstBinding? = null + private val binding get() = _binding!! + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + _binding = FragmentFirstBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.buttonFirst.setOnClickListener { + val bundle = bundleOf("city" to binding.citySpinner.selectedItem.toString()) + findNavController().navigate(R.id.action_FirstFragment_to_SecondFragment, bundle) + } + + ArrayAdapter.createFromResource( + view.context, + R.array.city_array, + android.R.layout.simple_spinner_item + ).also { adapter -> + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) + binding.citySpinner.adapter = adapter + } + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } +} \ No newline at end of file diff --git a/sample-app/app/src/main/java/co/elastic/apm/android/sample/MainActivity.kt b/sample-app/app/src/main/java/co/elastic/apm/android/sample/MainActivity.kt new file mode 100644 index 00000000..e29a90b7 --- /dev/null +++ b/sample-app/app/src/main/java/co/elastic/apm/android/sample/MainActivity.kt @@ -0,0 +1,38 @@ +package co.elastic.apm.android.sample + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import androidx.navigation.findNavController +import androidx.navigation.ui.AppBarConfiguration +import androidx.navigation.ui.setupActionBarWithNavController +import co.elastic.apm.android.sample.databinding.ActivityMainBinding +import com.google.android.material.snackbar.Snackbar + +class MainActivity : AppCompatActivity() { + + private lateinit var appBarConfiguration: AppBarConfiguration + private lateinit var binding: ActivityMainBinding + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + binding = ActivityMainBinding.inflate(layoutInflater) + setContentView(binding.root) + + setSupportActionBar(binding.toolbar) + + val navController = findNavController(R.id.nav_host_fragment_content_main) + appBarConfiguration = AppBarConfiguration(navController.graph) + setupActionBarWithNavController(navController, appBarConfiguration) + + binding.fab.setOnClickListener { view -> + Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG) + .setAction("Action", null).show() + } + } + + override fun onSupportNavigateUp(): Boolean { + val navController = findNavController(R.id.nav_host_fragment_content_main) + return navController.navigateUp() || super.onSupportNavigateUp() + } +} \ No newline at end of file diff --git a/sample-app/app/src/main/java/co/elastic/apm/android/sample/MyApp.kt b/sample-app/app/src/main/java/co/elastic/apm/android/sample/MyApp.kt new file mode 100644 index 00000000..9d22c9ab --- /dev/null +++ b/sample-app/app/src/main/java/co/elastic/apm/android/sample/MyApp.kt @@ -0,0 +1,12 @@ +package co.elastic.apm.android.sample + +import android.app.Application +import co.elastic.apm.android.sdk.ElasticApmAgent + +class MyApp : Application() { + + override fun onCreate() { + super.onCreate() + ElasticApmAgent.initialize(this) + } +} \ No newline at end of file diff --git a/sample-app/app/src/main/java/co/elastic/apm/android/sample/SecondFragment.kt b/sample-app/app/src/main/java/co/elastic/apm/android/sample/SecondFragment.kt new file mode 100644 index 00000000..c6c8317b --- /dev/null +++ b/sample-app/app/src/main/java/co/elastic/apm/android/sample/SecondFragment.kt @@ -0,0 +1,70 @@ +package co.elastic.apm.android.sample + +import android.os.Bundle +import android.text.Html +import android.text.method.LinkMovementMethod +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Toast +import androidx.fragment.app.Fragment +import androidx.lifecycle.lifecycleScope +import androidx.navigation.fragment.findNavController +import co.elastic.apm.android.sample.databinding.FragmentSecondBinding +import co.elastic.apm.android.sample.network.WeatherRestManager +import co.elastic.apm.android.sample.network.data.ForecastResponse +import kotlinx.coroutines.launch + +class SecondFragment : Fragment() { + + private var _binding: FragmentSecondBinding? = null + private val binding get() = _binding!! + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + _binding = FragmentSecondBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + lifecycleScope.launch { + try { + val city = arguments?.getString("city") ?: "Berlin" + binding.temperatureTitle.text = getString( + R.string.temperature_title, + city + ) + updateTemperature(WeatherRestManager.getCurrentCityWeather(city)) + showApiNotice() + } catch (e: Exception) { + e.printStackTrace() + Toast.makeText(requireContext(), R.string.unknown_error_message, Toast.LENGTH_SHORT) + .show() + } + } + + binding.buttonSecond.setOnClickListener { + findNavController().navigate(R.id.action_SecondFragment_to_FirstFragment) + } + } + + private fun showApiNotice() { + binding.txtApiNotice.movementMethod = LinkMovementMethod.getInstance() + binding.txtApiNotice.text = Html.fromHtml(getString(R.string.weather_api_notice_message)) + } + + private fun updateTemperature(response: ForecastResponse) { + binding.txtDegreesCelsius.text = getString( + R.string.temperature_in_celsius, + response.currentWeather.temperature + ) + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } +} \ No newline at end of file diff --git a/sample-app/app/src/main/java/co/elastic/apm/android/sample/network/CityWeatherService.kt b/sample-app/app/src/main/java/co/elastic/apm/android/sample/network/CityWeatherService.kt new file mode 100644 index 00000000..d7a34e8e --- /dev/null +++ b/sample-app/app/src/main/java/co/elastic/apm/android/sample/network/CityWeatherService.kt @@ -0,0 +1,12 @@ +package co.elastic.apm.android.sample.network + +import co.elastic.apm.android.sample.network.data.ForecastResponse +import retrofit2.http.GET +import retrofit2.http.Query + +interface CityWeatherService { + @GET("forecast") + suspend fun getCurrentWeather( + @Query("city") city: String + ): ForecastResponse +} \ No newline at end of file diff --git a/sample-app/app/src/main/java/co/elastic/apm/android/sample/network/WeatherRestManager.kt b/sample-app/app/src/main/java/co/elastic/apm/android/sample/network/WeatherRestManager.kt new file mode 100644 index 00000000..c67b4e6c --- /dev/null +++ b/sample-app/app/src/main/java/co/elastic/apm/android/sample/network/WeatherRestManager.kt @@ -0,0 +1,20 @@ +package co.elastic.apm.android.sample.network + +import co.elastic.apm.android.sample.network.data.ForecastResponse +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory + +object WeatherRestManager { + + private val service: CityWeatherService by lazy { + val retrofit = Retrofit.Builder() + .baseUrl("http://10.0.2.2:8080/v1/") + .addConverterFactory(GsonConverterFactory.create()) + .build() + retrofit.create(CityWeatherService::class.java) + } + + suspend fun getCurrentCityWeather(city: String): ForecastResponse { + return service.getCurrentWeather(city) + } +} \ No newline at end of file diff --git a/sample-app/app/src/main/java/co/elastic/apm/android/sample/network/data/CurrentWeatherResponse.kt b/sample-app/app/src/main/java/co/elastic/apm/android/sample/network/data/CurrentWeatherResponse.kt new file mode 100644 index 00000000..be3d5f43 --- /dev/null +++ b/sample-app/app/src/main/java/co/elastic/apm/android/sample/network/data/CurrentWeatherResponse.kt @@ -0,0 +1,3 @@ +package co.elastic.apm.android.sample.network.data + +data class CurrentWeatherResponse(val temperature: Double) \ No newline at end of file diff --git a/sample-app/app/src/main/java/co/elastic/apm/android/sample/network/data/ForecastResponse.kt b/sample-app/app/src/main/java/co/elastic/apm/android/sample/network/data/ForecastResponse.kt new file mode 100644 index 00000000..83ae69c3 --- /dev/null +++ b/sample-app/app/src/main/java/co/elastic/apm/android/sample/network/data/ForecastResponse.kt @@ -0,0 +1,8 @@ +package co.elastic.apm.android.sample.network.data + +import com.google.gson.annotations.SerializedName + +data class ForecastResponse( + @SerializedName("current_weather") + val currentWeather: CurrentWeatherResponse +) \ No newline at end of file diff --git a/sample-app/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/sample-app/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 00000000..2b068d11 --- /dev/null +++ b/sample-app/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/sample-app/app/src/main/res/drawable/ic_launcher_background.xml b/sample-app/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 00000000..07d5da9c --- /dev/null +++ b/sample-app/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sample-app/app/src/main/res/layout/activity_main.xml b/sample-app/app/src/main/res/layout/activity_main.xml new file mode 100644 index 00000000..12954737 --- /dev/null +++ b/sample-app/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/sample-app/app/src/main/res/layout/content_main.xml b/sample-app/app/src/main/res/layout/content_main.xml new file mode 100644 index 00000000..e416e1c1 --- /dev/null +++ b/sample-app/app/src/main/res/layout/content_main.xml @@ -0,0 +1,19 @@ + + + + + \ No newline at end of file diff --git a/sample-app/app/src/main/res/layout/fragment_first.xml b/sample-app/app/src/main/res/layout/fragment_first.xml new file mode 100644 index 00000000..545ed982 --- /dev/null +++ b/sample-app/app/src/main/res/layout/fragment_first.xml @@ -0,0 +1,43 @@ + + + + + +