Migrate from coil2 to coil3

- Use BOM platform dependency.
- Remove -kt suffix from dependency name has they serve no real purpose.
- Add `coil-network` artifact to configure `CallFactory`.
- Although "First party Decoder" `SvgDecoder` is now automatically added to each new ImageLoader through a service loader, keeping it in code prevents us from removing the dependency that would appear unused otherwise.
- Replace explicit `respectCacheHeaders(false)` with `CacheStrategy::DEFAULT` which does not respect cache-control headers.
- Migrate `ImageLoaderFactory` to `SingletonImageLoader.Factory`
- Inline TOML dependencies (see https://github.com/android/nowinandroid/pull/1856)

docs: https://coil-kt.github.io/coil/upgrading_to_coil3/
pull/1860/head
Simon Marquis 6 months ago
parent 689ef92e41
commit d859b3cfc7

@ -4,7 +4,7 @@ androidx.activity:activity:1.9.3
androidx.annotation:annotation-experimental:1.4.1 androidx.annotation:annotation-experimental:1.4.1
androidx.annotation:annotation-jvm:1.9.1 androidx.annotation:annotation-jvm:1.9.1
androidx.annotation:annotation:1.9.1 androidx.annotation:annotation:1.9.1
androidx.appcompat:appcompat-resources:1.6.1 androidx.appcompat:appcompat-resources:1.7.0
androidx.arch.core:core-common:2.2.0 androidx.arch.core:core-common:2.2.0
androidx.arch.core:core-runtime:2.2.0 androidx.arch.core:core-runtime:2.2.0
androidx.autofill:autofill:1.0.0 androidx.autofill:autofill:1.0.0
@ -52,8 +52,8 @@ androidx.compose.ui:ui-util:1.8.0-beta02
androidx.compose.ui:ui:1.8.0-beta02 androidx.compose.ui:ui:1.8.0-beta02
androidx.compose:compose-bom-alpha:2025.02.00 androidx.compose:compose-bom-alpha:2025.02.00
androidx.concurrent:concurrent-futures:1.1.0 androidx.concurrent:concurrent-futures:1.1.0
androidx.core:core-ktx:1.13.1 androidx.core:core-ktx:1.15.0
androidx.core:core:1.13.1 androidx.core:core:1.15.0
androidx.customview:customview-poolingcontainer:1.0.0 androidx.customview:customview-poolingcontainer:1.0.0
androidx.customview:customview:1.0.0 androidx.customview:customview:1.0.0
androidx.emoji2:emoji2:1.4.0 androidx.emoji2:emoji2:1.4.0
@ -82,7 +82,7 @@ androidx.lifecycle:lifecycle-viewmodel-savedstate:2.8.7
androidx.lifecycle:lifecycle-viewmodel:2.8.7 androidx.lifecycle:lifecycle-viewmodel:2.8.7
androidx.loader:loader:1.0.0 androidx.loader:loader:1.0.0
androidx.metrics:metrics-performance:1.0.0-beta01 androidx.metrics:metrics-performance:1.0.0-beta01
androidx.profileinstaller:profileinstaller:1.4.0 androidx.profileinstaller:profileinstaller:1.4.1
androidx.savedstate:savedstate-ktx:1.2.1 androidx.savedstate:savedstate-ktx:1.2.1
androidx.savedstate:savedstate:1.2.1 androidx.savedstate:savedstate:1.2.1
androidx.startup:startup-runtime:1.1.1 androidx.startup:startup-runtime:1.1.1
@ -96,30 +96,52 @@ androidx.window.extensions.core:core:1.0.0
androidx.window:window-core-android:1.3.0 androidx.window:window-core-android:1.3.0
androidx.window:window-core:1.3.0 androidx.window:window-core:1.3.0
androidx.window:window:1.3.0 androidx.window:window:1.3.0
com.google.accompanist:accompanist-drawablepainter:0.32.0 com.google.accompanist:accompanist-drawablepainter:0.36.0
com.google.code.findbugs:jsr305:3.0.2 com.google.code.findbugs:jsr305:3.0.2
com.google.dagger:dagger-lint-aar:2.56 com.google.dagger:dagger-lint-aar:2.56
com.google.dagger:dagger:2.56 com.google.dagger:dagger:2.56
com.google.dagger:hilt-android:2.56 com.google.dagger:hilt-android:2.56
com.google.dagger:hilt-core:2.56 com.google.dagger:hilt-core:2.56
com.google.guava:listenablefuture:1.0 com.google.guava:listenablefuture:1.0
com.squareup.okhttp3:okhttp:4.12.0 com.squareup.okio:okio-jvm:3.10.2
com.squareup.okio:okio-jvm:3.9.0 com.squareup.okio:okio:3.10.2
com.squareup.okio:okio:3.9.0 io.coil-kt.coil3:coil-android:3.1.0
io.coil-kt:coil-base:2.7.0 io.coil-kt.coil3:coil-bom:3.1.0
io.coil-kt:coil-compose-base:2.7.0 io.coil-kt.coil3:coil-compose-android:3.1.0
io.coil-kt:coil-compose:2.7.0 io.coil-kt.coil3:coil-compose-core-android:3.1.0
io.coil-kt:coil:2.7.0 io.coil-kt.coil3:coil-compose-core:3.1.0
io.coil-kt.coil3:coil-compose:3.1.0
io.coil-kt.coil3:coil-core-android:3.1.0
io.coil-kt.coil3:coil-core:3.1.0
io.coil-kt.coil3:coil:3.1.0
jakarta.inject:jakarta.inject-api:2.0.1 jakarta.inject:jakarta.inject-api:2.0.1
javax.inject:javax.inject:1 javax.inject:javax.inject:1
org.jetbrains.androidx.lifecycle:lifecycle-common:2.8.4
org.jetbrains.androidx.lifecycle:lifecycle-runtime-compose:2.8.4
org.jetbrains.androidx.lifecycle:lifecycle-runtime:2.8.4
org.jetbrains.androidx.lifecycle:lifecycle-viewmodel:2.8.4
org.jetbrains.compose.animation:animation-core:1.7.3
org.jetbrains.compose.animation:animation:1.7.3
org.jetbrains.compose.annotation-internal:annotation:1.7.3
org.jetbrains.compose.collection-internal:collection:1.7.3
org.jetbrains.compose.foundation:foundation-layout:1.7.3
org.jetbrains.compose.foundation:foundation:1.7.3
org.jetbrains.compose.runtime:runtime-saveable:1.7.3
org.jetbrains.compose.runtime:runtime:1.7.3
org.jetbrains.compose.ui:ui-geometry:1.7.3
org.jetbrains.compose.ui:ui-graphics:1.7.3
org.jetbrains.compose.ui:ui-text:1.7.3
org.jetbrains.compose.ui:ui-unit:1.7.3
org.jetbrains.compose.ui:ui-util:1.7.3
org.jetbrains.compose.ui:ui:1.7.3
org.jetbrains.kotlin:kotlin-stdlib-common:2.1.10 org.jetbrains.kotlin:kotlin-stdlib-common:2.1.10
org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.9.0
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.0
org.jetbrains.kotlin:kotlin-stdlib:2.1.10 org.jetbrains.kotlin:kotlin-stdlib:2.1.10
org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1 org.jetbrains.kotlinx:atomicfu-jvm:0.23.2
org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.8.1 org.jetbrains.kotlinx:atomicfu:0.23.2
org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.8.1 org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.1
org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1 org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.10.1
org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.10.1
org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.1
org.jetbrains.kotlinx:kotlinx-datetime-jvm:0.6.1 org.jetbrains.kotlinx:kotlinx-datetime-jvm:0.6.1
org.jetbrains.kotlinx:kotlinx-datetime:0.6.1 org.jetbrains.kotlinx:kotlinx-datetime:0.6.1
org.jetbrains:annotations:23.0.0 org.jetbrains:annotations:23.0.0

@ -101,7 +101,8 @@ dependencies {
implementation(libs.androidx.tracing.ktx) implementation(libs.androidx.tracing.ktx)
implementation(libs.androidx.window.core) implementation(libs.androidx.window.core)
implementation(libs.kotlinx.coroutines.guava) implementation(libs.kotlinx.coroutines.guava)
implementation(libs.coil.kt) implementation(platform(libs.coil.bom))
implementation(libs.coil)
implementation(libs.kotlinx.serialization.json) implementation(libs.kotlinx.serialization.json)
ksp(libs.hilt.compiler) ksp(libs.hilt.compiler)

@ -147,7 +147,7 @@ androidx.window:window:1.3.0
androidx.work:work-runtime-ktx:2.10.0 androidx.work:work-runtime-ktx:2.10.0
androidx.work:work-runtime:2.10.0 androidx.work:work-runtime:2.10.0
com.caverock:androidsvg-aar:1.4 com.caverock:androidsvg-aar:1.4
com.google.accompanist:accompanist-drawablepainter:0.32.0 com.google.accompanist:accompanist-drawablepainter:0.36.0
com.google.accompanist:accompanist-permissions:0.37.0 com.google.accompanist:accompanist-permissions:0.37.0
com.google.android.datatransport:transport-api:3.2.0 com.google.android.datatransport:transport-api:3.2.0
com.google.android.datatransport:transport-backend-cct:3.3.0 com.google.android.datatransport:transport-backend-cct:3.3.0
@ -200,24 +200,54 @@ com.google.protobuf:protobuf-javalite:4.29.2
com.google.protobuf:protobuf-kotlin-lite:4.29.2 com.google.protobuf:protobuf-kotlin-lite:4.29.2
com.squareup.okhttp3:logging-interceptor:4.12.0 com.squareup.okhttp3:logging-interceptor:4.12.0
com.squareup.okhttp3:okhttp:4.12.0 com.squareup.okhttp3:okhttp:4.12.0
com.squareup.okio:okio-jvm:3.9.0 com.squareup.okio:okio-jvm:3.10.2
com.squareup.okio:okio:3.9.0 com.squareup.okio:okio:3.10.2
com.squareup.retrofit2:converter-kotlinx-serialization:2.11.0 com.squareup.retrofit2:converter-kotlinx-serialization:2.11.0
com.squareup.retrofit2:retrofit:2.11.0 com.squareup.retrofit2:retrofit:2.11.0
io.coil-kt:coil-base:2.7.0 io.coil-kt.coil3:coil-android:3.1.0
io.coil-kt:coil-compose-base:2.7.0 io.coil-kt.coil3:coil-bom:3.1.0
io.coil-kt:coil-compose:2.7.0 io.coil-kt.coil3:coil-compose-android:3.1.0
io.coil-kt:coil-svg:2.7.0 io.coil-kt.coil3:coil-compose-core-android:3.1.0
io.coil-kt:coil:2.7.0 io.coil-kt.coil3:coil-compose-core:3.1.0
io.coil-kt.coil3:coil-compose:3.1.0
io.coil-kt.coil3:coil-core-android:3.1.0
io.coil-kt.coil3:coil-core:3.1.0
io.coil-kt.coil3:coil-network-core-android:3.1.0
io.coil-kt.coil3:coil-network-core:3.1.0
io.coil-kt.coil3:coil-network-okhttp-jvm:3.1.0
io.coil-kt.coil3:coil-network-okhttp:3.1.0
io.coil-kt.coil3:coil-svg-android:3.1.0
io.coil-kt.coil3:coil-svg:3.1.0
io.coil-kt.coil3:coil:3.1.0
jakarta.inject:jakarta.inject-api:2.0.1 jakarta.inject:jakarta.inject-api:2.0.1
javax.inject:javax.inject:1 javax.inject:javax.inject:1
org.checkerframework:checker-qual:3.12.0 org.checkerframework:checker-qual:3.12.0
org.jetbrains.androidx.lifecycle:lifecycle-common:2.8.4
org.jetbrains.androidx.lifecycle:lifecycle-runtime-compose:2.8.4
org.jetbrains.androidx.lifecycle:lifecycle-runtime:2.8.4
org.jetbrains.androidx.lifecycle:lifecycle-viewmodel:2.8.4
org.jetbrains.compose.animation:animation-core:1.7.3
org.jetbrains.compose.animation:animation:1.7.3
org.jetbrains.compose.annotation-internal:annotation:1.7.3
org.jetbrains.compose.collection-internal:collection:1.7.3
org.jetbrains.compose.foundation:foundation-layout:1.7.3
org.jetbrains.compose.foundation:foundation:1.7.3
org.jetbrains.compose.runtime:runtime-saveable:1.7.3
org.jetbrains.compose.runtime:runtime:1.7.3
org.jetbrains.compose.ui:ui-geometry:1.7.3
org.jetbrains.compose.ui:ui-graphics:1.7.3
org.jetbrains.compose.ui:ui-text:1.7.3
org.jetbrains.compose.ui:ui-unit:1.7.3
org.jetbrains.compose.ui:ui-util:1.7.3
org.jetbrains.compose.ui:ui:1.7.3
org.jetbrains.kotlin:kotlin-android-extensions-runtime:1.9.22 org.jetbrains.kotlin:kotlin-android-extensions-runtime:1.9.22
org.jetbrains.kotlin:kotlin-parcelize-runtime:1.9.22 org.jetbrains.kotlin:kotlin-parcelize-runtime:1.9.22
org.jetbrains.kotlin:kotlin-stdlib-common:2.1.10 org.jetbrains.kotlin:kotlin-stdlib-common:2.1.10
org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.9.0 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.22
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.0 org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.22
org.jetbrains.kotlin:kotlin-stdlib:2.1.10 org.jetbrains.kotlin:kotlin-stdlib:2.1.10
org.jetbrains.kotlinx:atomicfu-jvm:0.23.2
org.jetbrains.kotlinx:atomicfu:0.23.2
org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.1 org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.1
org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.10.1 org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.10.1
org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.10.1 org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.10.1

@ -20,8 +20,9 @@ import android.app.Application
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
import coil.ImageLoader import coil3.ImageLoader
import coil.ImageLoaderFactory import coil3.PlatformContext
import coil3.SingletonImageLoader
import com.google.samples.apps.nowinandroid.sync.initializers.Sync 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
@ -31,7 +32,7 @@ import javax.inject.Inject
* [Application] class for NiA * [Application] class for NiA
*/ */
@HiltAndroidApp @HiltAndroidApp
class NiaApplication : Application(), ImageLoaderFactory { class NiaApplication : Application(), SingletonImageLoader.Factory {
@Inject @Inject
lateinit var imageLoader: dagger.Lazy<ImageLoader> lateinit var imageLoader: dagger.Lazy<ImageLoader>
@ -48,7 +49,7 @@ class NiaApplication : Application(), ImageLoaderFactory {
profileVerifierLogger() profileVerifierLogger()
} }
override fun newImageLoader(): ImageLoader = imageLoader.get() override fun newImageLoader(context: PlatformContext): ImageLoader = imageLoader.get()
/** /**
* Return true if the application is debuggable. * Return true if the application is debuggable.

@ -36,7 +36,8 @@ dependencies {
api(libs.androidx.compose.runtime) api(libs.androidx.compose.runtime)
api(libs.androidx.compose.ui.util) api(libs.androidx.compose.ui.util)
implementation(libs.coil.kt.compose) implementation(platform(libs.coil.bom))
implementation(libs.coil.compose)
testImplementation(libs.androidx.compose.ui.test) testImplementation(libs.androidx.compose.ui.test)
testImplementation(libs.androidx.compose.ui.testManifest) testImplementation(libs.androidx.compose.ui.testManifest)

@ -35,10 +35,10 @@ import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalInspectionMode import androidx.compose.ui.platform.LocalInspectionMode
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import coil.compose.AsyncImage import coil3.compose.AsyncImage
import coil.compose.AsyncImagePainter.State.Error import coil3.compose.AsyncImagePainter.State.Error
import coil.compose.AsyncImagePainter.State.Loading import coil3.compose.AsyncImagePainter.State.Loading
import coil.compose.rememberAsyncImagePainter import coil3.compose.rememberAsyncImagePainter
import com.google.samples.apps.nowinandroid.core.designsystem.R import com.google.samples.apps.nowinandroid.core.designsystem.R
import com.google.samples.apps.nowinandroid.core.designsystem.theme.LocalTintTheme import com.google.samples.apps.nowinandroid.core.designsystem.theme.LocalTintTheme

@ -43,8 +43,9 @@ dependencies {
api(projects.core.common) api(projects.core.common)
api(projects.core.model) api(projects.core.model)
implementation(libs.coil.kt) implementation(platform(libs.coil.bom))
implementation(libs.coil.kt.svg) implementation(libs.coil.network)
implementation(libs.coil.svg)
implementation(libs.kotlinx.serialization.json) implementation(libs.kotlinx.serialization.json)
implementation(libs.okhttp.logging) implementation(libs.okhttp.logging)
implementation(libs.retrofit.core) implementation(libs.retrofit.core)

@ -18,9 +18,12 @@ package com.google.samples.apps.nowinandroid.core.network.di
import android.content.Context import android.content.Context
import androidx.tracing.trace import androidx.tracing.trace
import coil.ImageLoader import coil3.ImageLoader
import coil.decode.SvgDecoder import coil3.annotation.ExperimentalCoilApi
import coil.util.DebugLogger import coil3.network.CacheStrategy
import coil3.network.okhttp.OkHttpNetworkFetcherFactory
import coil3.svg.SvgDecoder
import coil3.util.DebugLogger
import com.google.samples.apps.nowinandroid.core.network.BuildConfig import com.google.samples.apps.nowinandroid.core.network.BuildConfig
import com.google.samples.apps.nowinandroid.core.network.demo.DemoAssetManager import com.google.samples.apps.nowinandroid.core.network.demo.DemoAssetManager
import dagger.Module import dagger.Module
@ -80,11 +83,18 @@ internal object NetworkModule {
@ApplicationContext application: Context, @ApplicationContext application: Context,
): ImageLoader = trace("NiaImageLoader") { ): ImageLoader = trace("NiaImageLoader") {
ImageLoader.Builder(application) ImageLoader.Builder(application)
.callFactory { okHttpCallFactory.get() } .components {
.components { add(SvgDecoder.Factory()) } add(
// Assume most content images are versioned urls @OptIn(ExperimentalCoilApi::class)
// but some problematic images are fetching each time OkHttpNetworkFetcherFactory(
.respectCacheHeaders(false) callFactory = okHttpCallFactory::get,
// Assume most content images are versioned urls
// but some problematic images are fetching each time
cacheStrategy = CacheStrategy::DEFAULT,
),
)
add(SvgDecoder.Factory())
}
.apply { .apply {
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
logger(DebugLogger()) logger(DebugLogger())

@ -30,8 +30,8 @@ dependencies {
api(projects.core.model) api(projects.core.model)
implementation(libs.androidx.browser) implementation(libs.androidx.browser)
implementation(libs.coil.kt) implementation(platform(libs.coil.bom))
implementation(libs.coil.kt.compose) implementation(libs.coil.compose)
androidTestImplementation(libs.bundles.androidx.compose.ui.test) androidTestImplementation(libs.bundles.androidx.compose.ui.test)
androidTestImplementation(projects.core.testing) androidTestImplementation(projects.core.testing)

@ -64,8 +64,8 @@ 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 coil.compose.AsyncImagePainter import coil3.compose.AsyncImagePainter
import coil.compose.rememberAsyncImagePainter import coil3.compose.rememberAsyncImagePainter
import com.google.samples.apps.nowinandroid.core.designsystem.R.drawable import com.google.samples.apps.nowinandroid.core.designsystem.R.drawable
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaIconToggleButton import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaIconToggleButton
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaTopicTag import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaTopicTag

@ -30,7 +30,6 @@ androidxTracing = "1.3.0-alpha02"
androidxUiAutomator = "2.3.0" androidxUiAutomator = "2.3.0"
androidxWindowManager = "1.3.0" androidxWindowManager = "1.3.0"
androidxWork = "2.10.0" androidxWork = "2.10.0"
coil = "2.7.0"
dependencyGuard = "0.5.0" dependencyGuard = "0.5.0"
firebaseBom = "33.7.0" firebaseBom = "33.7.0"
firebaseCrashlyticsPlugin = "3.0.2" firebaseCrashlyticsPlugin = "3.0.2"
@ -110,9 +109,11 @@ androidx-tracing-ktx = { group = "androidx.tracing", name = "tracing-ktx", versi
androidx-window-core = { group = "androidx.window", name = "window-core", version.ref = "androidxWindowManager" } androidx-window-core = { group = "androidx.window", name = "window-core", version.ref = "androidxWindowManager" }
androidx-work-ktx = { group = "androidx.work", name = "work-runtime-ktx", version.ref = "androidxWork" } androidx-work-ktx = { group = "androidx.work", name = "work-runtime-ktx", version.ref = "androidxWork" }
androidx-work-testing = { group = "androidx.work", name = "work-testing", version.ref = "androidxWork" } androidx-work-testing = { group = "androidx.work", name = "work-testing", version.ref = "androidxWork" }
coil-kt = { group = "io.coil-kt", name = "coil", version.ref = "coil" } coil = { module = "io.coil-kt.coil3:coil" }
coil-kt-compose = { group = "io.coil-kt", name = "coil-compose", version.ref = "coil" } coil-bom = "io.coil-kt.coil3:coil-bom:3.1.0"
coil-kt-svg = { group = "io.coil-kt", name = "coil-svg", version.ref = "coil" } coil-compose = { module = "io.coil-kt.coil3:coil-compose" }
coil-network = { module = "io.coil-kt.coil3:coil-network-okhttp" }
coil-svg = { module = "io.coil-kt.coil3:coil-svg" }
firebase-analytics = { group = "com.google.firebase", name = "firebase-analytics" } firebase-analytics = { group = "com.google.firebase", name = "firebase-analytics" }
firebase-bom = { group = "com.google.firebase", name = "firebase-bom", version.ref = "firebaseBom" } firebase-bom = { group = "com.google.firebase", name = "firebase-bom", version.ref = "firebaseBom" }
firebase-cloud-messaging = { group = "com.google.firebase", name = "firebase-messaging" } firebase-cloud-messaging = { group = "com.google.firebase", name = "firebase-messaging" }

Loading…
Cancel
Save