Merge remote-tracking branch 'origin/ioc-workshop' into ioc-workshop

ioc-workshop
raystatic 2 months ago
commit dd70a1981c
No known key found for this signature in database

@ -56,6 +56,15 @@ android {
// Ensure Baseline Profile is fresh for release builds. // Ensure Baseline Profile is fresh for release builds.
baselineProfile.automaticGenerationDuringBuild = true baselineProfile.automaticGenerationDuringBuild = true
} }
create("benchmark") {
initWith(getByName("release"))
matchingFallbacks += listOf("release")
applicationIdSuffix = NiaBuildType.BENCHMARK.applicationIdSuffix
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
}
} }
packaging { packaging {
@ -79,6 +88,7 @@ dependencies {
implementation(projects.feature.search.api) implementation(projects.feature.search.api)
implementation(projects.feature.search.impl) implementation(projects.feature.search.impl)
implementation(projects.feature.settings.impl) implementation(projects.feature.settings.impl)
implementation(projects.mylibrary)
implementation(projects.core.common) implementation(projects.core.common)
implementation(projects.core.ui) implementation(projects.core.ui)

@ -17,6 +17,7 @@
package com.google.samples.apps.nowinandroid package com.google.samples.apps.nowinandroid
import android.app.Application import android.app.Application
import android.content.ComponentCallbacks2
import android.content.pm.ApplicationInfo import android.content.pm.ApplicationInfo
import android.os.StrictMode import android.os.StrictMode
import android.os.StrictMode.ThreadPolicy.Builder import android.os.StrictMode.ThreadPolicy.Builder
@ -26,12 +27,21 @@ import com.google.samples.apps.nowinandroid.sync.initializers.Sync
import com.google.samples.apps.nowinandroid.util.ProfileVerifierLogger import com.google.samples.apps.nowinandroid.util.ProfileVerifierLogger
import dagger.hilt.android.HiltAndroidApp import dagger.hilt.android.HiltAndroidApp
import javax.inject.Inject import javax.inject.Inject
import com.example.mylibrary.StartupTask
import com.example.mylibrary.TaskRunner
import android.util.Log
class NiaStartupTask : StartupTask {
override fun run() {
Log.d("NiaStartupTask", "Running startup task from mylibrary")
}
}
/** /**
* [Application] class for NiA * [Application] class for NiA
*/ */
@HiltAndroidApp @HiltAndroidApp
class NiaApplication : Application(), ImageLoaderFactory { class NiaApplication : Application(), ImageLoaderFactory, ComponentCallbacks2 {
@Inject @Inject
lateinit var imageLoader: dagger.Lazy<ImageLoader> lateinit var imageLoader: dagger.Lazy<ImageLoader>
@ -41,11 +51,10 @@ class NiaApplication : Application(), ImageLoaderFactory {
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
setStrictModePolicy()
// Initialize Sync; the system responsible for keeping data in the app up to date. // Initialize Sync; the system responsible for keeping data in the app up to date.
Sync.initialize(context = this) Sync.initialize(context = this)
profileVerifierLogger() profileVerifierLogger()
TaskRunner.execute(NiaStartupTask::class.java)
} }
override fun newImageLoader(): ImageLoader = imageLoader.get() override fun newImageLoader(): ImageLoader = imageLoader.get()
@ -57,6 +66,27 @@ class NiaApplication : Application(), ImageLoaderFactory {
return 0 != applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE return 0 != applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE
} }
override fun onTrimMemory(level: Int) {
super<Application>.onTrimMemory(level)
// 1. Explicitly clear or trim our ImageLoader's memory cache
// (Note: Coil automatically listens to ComponentCallbacks2, but this serves as a good
// example of how to manually trim caches based on system memory signals).
if (level == TRIM_MEMORY_UI_HIDDEN){
// The user has navigated away from the app and the UI is no longer visible.
// This is a good place to release large UI-related resources that you
// won't need while in the background
imageLoader.get().memoryCache?.trimMemory(level)
}
if(level == TRIM_MEMORY_BACKGROUND) {
// The app is in the background and the system is running low on memory.
// As the level increases to COMPLETE, the likelihood of the app process
// being killed increases. Aggressively clear caches here.
imageLoader.get().memoryCache?.clear()
}
}
/** /**
* Set a thread policy that detects all potential problems on the main thread, such as network * Set a thread policy that detects all potential problems on the main thread, such as network
* and disk access. * and disk access.

@ -22,4 +22,5 @@ package com.google.samples.apps.nowinandroid
enum class NiaBuildType(val applicationIdSuffix: String? = null) { enum class NiaBuildType(val applicationIdSuffix: String? = null) {
DEBUG(".debug"), DEBUG(".debug"),
RELEASE, RELEASE,
BENCHMARK(".benchmark")
} }

@ -81,7 +81,11 @@ internal object NetworkModule {
): ImageLoader = trace("NiaImageLoader") { ): ImageLoader = trace("NiaImageLoader") {
ImageLoader.Builder(application) ImageLoader.Builder(application)
.callFactory { okHttpCallFactory.get() } .callFactory { okHttpCallFactory.get() }
.components { add(SvgDecoder.Factory()) } .memoryCache(null) // ❌ Bug 1: Memory cache disabled
.diskCache(null)
// ❌ Bug 2: Disk cache disabled
.allowHardware(false) // ❌ Bug 3: Forces bitmaps onto Java heap
.bitmapConfig(android.graphics.Bitmap.Config.ARGB_8888)
// Assume most content images are versioned urls // Assume most content images are versioned urls
// but some problematic images are fetching each time // but some problematic images are fetching each time
.respectCacheHeaders(false) .respectCacheHeaders(false)

@ -17,6 +17,7 @@
package com.google.samples.apps.nowinandroid.core.ui package com.google.samples.apps.nowinandroid.core.ui
import android.content.ClipData import android.content.ClipData
import android.graphics.Bitmap
import android.os.Build.VERSION import android.os.Build.VERSION
import android.os.Build.VERSION_CODES import android.os.Build.VERSION_CODES
import android.view.View import android.view.View
@ -53,7 +54,10 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draganddrop.DragAndDropTransferData import androidx.compose.ui.draganddrop.DragAndDropTransferData
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.graphics.painter.BitmapPainter
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalInspectionMode import androidx.compose.ui.platform.LocalInspectionMode
import androidx.compose.ui.platform.testTag import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
@ -64,6 +68,7 @@ import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat
import coil.compose.AsyncImagePainter import coil.compose.AsyncImagePainter
import coil.compose.rememberAsyncImagePainter import coil.compose.rememberAsyncImagePainter
import com.google.samples.apps.nowinandroid.core.designsystem.R.drawable import com.google.samples.apps.nowinandroid.core.designsystem.R.drawable
@ -189,10 +194,29 @@ fun NewsResourceHeaderImage(
}, },
) )
val isLocalInspection = LocalInspectionMode.current val isLocalInspection = LocalInspectionMode.current
val context = LocalContext.current
val perRowBitmap = remember {
Bitmap.createBitmap(
2000, 2000, Bitmap.Config.ARGB_8888
).apply {
val canvas = android.graphics.Canvas(this)
// Now you can use 'context' here to load the drawable
val drawable = ContextCompat.getDrawable(
context,
drawable.core_designsystem_ic_placeholder_default
)
drawable?.setBounds(0, 0, width, height)
drawable?.draw(canvas)
}.asImageBitmap()
}
Box( Box(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.height(180.dp), .height(320.dp),
contentAlignment = Alignment.Center, contentAlignment = Alignment.Center,
) { ) {
if (isLoading) { if (isLoading) {
@ -205,15 +229,16 @@ fun NewsResourceHeaderImage(
) )
} }
Image( Image(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.height(180.dp), .height(320.dp),
contentScale = ContentScale.Crop, contentScale = ContentScale.Crop,
painter = if (isError.not() && !isLocalInspection) { painter = if (isError.not() && !isLocalInspection) {
imageLoader imageLoader
} else { } else {
painterResource(drawable.core_designsystem_ic_placeholder_default) BitmapPainter(perRowBitmap)
}, },
// TODO b/226661685: Investigate using alt text of image to populate content description // TODO b/226661685: Investigate using alt text of image to populate content description
// decorative image, // decorative image,

@ -99,7 +99,7 @@ val FILTERS = listOf(
fun decodeSourceBitmap(context: Context): Bitmap { fun decodeSourceBitmap(context: Context): Bitmap {
val options = BitmapFactory.Options().apply { inSampleSize = 4 } val options = BitmapFactory.Options().apply { inSampleSize = 4 }
return BitmapFactory.decodeResource(context.resources, R.drawable.large_image, options) return BitmapFactory.decodeResource(context.resources, R.drawable.feature_interests_impl_large_image, options)
} }
fun createFilteredBitmap(source: Bitmap, filter: ImageFilter): Bitmap { fun createFilteredBitmap(source: Bitmap, filter: ImageFilter): Bitmap {

@ -0,0 +1,37 @@
plugins {
alias(libs.plugins.android.library)
}
android {
namespace = "com.example.mylibrary"
compileSdk = 36
defaultConfig {
minSdk = 21
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
isMinifyEnabled = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
}
dependencies {
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.appcompat)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.test.ext)
androidTestImplementation(libs.androidx.test.espresso.core)
}

@ -0,0 +1,12 @@
# Declare that the default constructor is called reflectively.
-keep class * implements com.example.mylibrary.StartupTask { <init>(); }
-keep class * {
public *;
}
-dontwarn com.example.mylibrary.StartupTask
-dontwarn com.example.mylibrary.TaskRunner

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

@ -0,0 +1,24 @@
package com.example.mylibrary
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.example.mylibrary.test", appContext.packageName)
}
}

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>

@ -0,0 +1,17 @@
package com.example.mylibrary
interface StartupTask {
fun run()
}
// The library object that loads and executes the task.
object TaskRunner {
fun execute(taskClassFromApp: Class<out StartupTask>) {
// R8 will remove the class specified by this string.
val taskClassInstance =
taskClassFromApp.getDeclaredConstructor().newInstance() as StartupTask
taskClassInstance.run()
}
}

@ -0,0 +1,17 @@
package com.example.mylibrary
import org.junit.Test
import org.junit.Assert.*
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}

@ -15,6 +15,17 @@
*/ */
pluginManagement { pluginManagement {
buildscript {
repositories {
mavenCentral()
maven {
url = uri("https://storage.googleapis.com/r8-releases/raw")
}
}
dependencies {
classpath("com.android.tools:r8:9.3.7-dev")
}
}
includeBuild("build-logic") includeBuild("build-logic")
repositories { repositories {
google { google {
@ -81,6 +92,7 @@ include(":lint")
include(":sync:work") include(":sync:work")
include(":sync:sync-test") include(":sync:sync-test")
include(":ui-test-hilt-manifest") include(":ui-test-hilt-manifest")
include(":mylibrary")
check(JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_17)) { check(JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_17)) {
""" """

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long
Loading…
Cancel
Save