Skip to content

Commit 2943210

Browse files
authored
fix: Fix any load flickers on Android (#96)
* fix: Fix any load flickers on Android * fix: Also support caching on Android * fix: Pass args * lint
1 parent c19464b commit 2943210

5 files changed

Lines changed: 58 additions & 53 deletions

File tree

packages/react-native-nitro-image/android/src/main/java/com/margelo/nitro/image/HybridImageLoader.kt

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,49 @@ package com.margelo.nitro.image
33
import androidx.annotation.Keep
44
import com.facebook.proguard.annotations.DoNotStrip
55
import com.margelo.nitro.core.Promise
6+
import kotlinx.coroutines.CoroutineScope
7+
import kotlinx.coroutines.Dispatchers
8+
import kotlinx.coroutines.launch
69

710
@Keep
811
@DoNotStrip
9-
class HybridImageLoader(private val loadImageFunc: () -> Promise<HybridImageSpec>): HybridImageLoaderSpec() {
10-
override fun loadImage(): Promise<HybridImageSpec> = loadImageFunc()
12+
class HybridImageLoader(
13+
private val loadImageFunc: () -> Promise<HybridImageSpec>,
14+
private val allowCaching: Boolean = true
15+
): HybridImageLoaderSpec() {
16+
private var cachedResult: HybridImageSpec? = null
17+
private val uiScope = CoroutineScope(Dispatchers.Main.immediate)
18+
19+
override fun loadImage(): Promise<HybridImageSpec> {
20+
if (allowCaching) {
21+
// We can cache the last loaded image in state, so future requests receive it instantly
22+
cachedResult?.let { cachedResult ->
23+
return Promise.resolved(cachedResult)
24+
}
25+
return loadImageFunc()
26+
.then { image ->
27+
this.cachedResult = image
28+
}
29+
} else {
30+
return loadImageFunc()
31+
}
32+
}
1133

1234
override fun requestImage(forView: HybridNitroImageViewSpec) {
1335
val view = forView as? HybridImageView ?: return
1436

1537
loadImage().then { maybeImage ->
1638
val image = maybeImage as? HybridImage ?: return@then
17-
view.imageView.setImageBitmap(image.bitmap)
39+
uiScope.launch {
40+
view.imageView.setImageBitmap(image.bitmap)
41+
}
1842
}
1943
}
2044

2145
override fun dropImage(forView: HybridNitroImageViewSpec) {
2246
val view = forView as? HybridImageView ?: return
23-
view.imageView.setImageDrawable(null)
47+
uiScope.launch {
48+
view.imageView.setImageDrawable(null)
49+
}
2450
}
25-
}
51+
}

packages/react-native-nitro-image/android/src/main/java/com/margelo/nitro/image/HybridImageLoaderFactory.kt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,22 +11,22 @@ class HybridImageLoaderFactory: HybridImageLoaderFactorySpec() {
1111
private val factory = HybridImageFactory()
1212

1313
override fun createFileImageLoader(filePath: String): HybridImageLoaderSpec {
14-
return HybridImageLoader { factory.loadFromFileAsync(filePath) }
14+
return HybridImageLoader({ factory.loadFromFileAsync(filePath) }, true)
1515
}
1616

1717
override fun createResourceImageLoader(name: String): HybridImageLoaderSpec {
18-
return HybridImageLoader { factory.loadFromResourcesAsync(name) }
18+
return HybridImageLoader({ factory.loadFromResourcesAsync(name) }, true)
1919
}
2020

2121
override fun createSymbolImageLoader(symbolName: String): HybridImageLoaderSpec {
22-
return HybridImageLoader { Promise.resolved(factory.loadFromSymbol(symbolName)) }
22+
return HybridImageLoader({ Promise.resolved(factory.loadFromSymbol(symbolName)) }, true)
2323
}
2424

2525
override fun createRawPixelDataImageLoader(data: RawPixelData): HybridImageLoaderSpec {
26-
return HybridImageLoader { factory.loadFromRawPixelDataAsync(data, false) }
26+
return HybridImageLoader({ factory.loadFromRawPixelDataAsync(data, false) }, true)
2727
}
2828

2929
override fun createEncodedImageDataImageLoader(data: EncodedImageData): HybridImageLoaderSpec {
30-
return HybridImageLoader { factory.loadFromEncodedImageDataAsync(data) }
30+
return HybridImageLoader({ factory.loadFromEncodedImageDataAsync(data) }, true)
3131
}
3232
}

packages/react-native-nitro-image/android/src/main/java/com/margelo/nitro/image/HybridImageView.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ class HybridImageView(context: Context): HybridNitroImageViewSpec(), RecyclableV
1818
companion object {
1919
private const val TAG = "HybridImageView"
2020
}
21-
private val uiScope = CoroutineScope(Dispatchers.Main)
21+
private val uiScope = CoroutineScope(Dispatchers.Main.immediate)
2222
private var resetImageBeforeLoad = false
2323

2424
val imageView = CustomImageView(context) { visible ->
@@ -66,15 +66,15 @@ class HybridImageView(context: Context): HybridNitroImageViewSpec(), RecyclableV
6666

6767
private fun updateImage() {
6868
image?.match(
69-
{ actualImage ->
69+
{ actualImage: HybridImageSpec ->
7070
// Image
7171
if (actualImage is HybridImage) {
7272
imageView.setImageBitmap(actualImage.bitmap)
7373
} else {
7474
throw Error("Image is a different type than HybridImage!")
7575
}
7676
},
77-
{ imageLoader ->
77+
{ _: HybridImageLoaderSpec ->
7878
// ImageLoader
7979
onAppear()
8080
}
Lines changed: 11 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//
22
// HybridImageLoader.swift
3-
// Pods
3+
// NitroImage
44
//
55
// Created by Marc Rousavy on 25.07.25.
66
//
@@ -9,28 +9,13 @@ import NitroModules
99

1010
class HybridImageLoader: HybridImageLoaderSpec {
1111
typealias LoadFunc = () throws -> Promise<any HybridImageSpec>
12-
typealias RequestFunc = (_ for: any HybridNitroImageViewSpec) throws -> Void
13-
typealias DropFunc = (_ for: any HybridNitroImageViewSpec) throws -> Void
1412

1513
private let load: LoadFunc
16-
private let requestImage: RequestFunc?
17-
private let dropImage: DropFunc?
1814
private let allowCaching: Bool
1915
private var cachedResult: (any HybridImageSpec)? = nil
2016

2117
init(load: @escaping LoadFunc, allowCaching: Bool = true) {
2218
self.load = load
23-
self.requestImage = nil
24-
self.dropImage = nil
25-
self.allowCaching = allowCaching
26-
}
27-
init(load: @escaping LoadFunc,
28-
requestImage: @escaping RequestFunc,
29-
dropImage: @escaping DropFunc,
30-
allowCaching: Bool = true) {
31-
self.load = load
32-
self.requestImage = requestImage
33-
self.dropImage = dropImage
3419
self.allowCaching = allowCaching
3520
}
3621

@@ -56,32 +41,20 @@ class HybridImageLoader: HybridImageLoaderSpec {
5641
}
5742

5843
func requestImage(forView view: any HybridNitroImageViewSpec) throws {
59-
if let requestImage {
60-
// Custom requestImage(...) func supplied
61-
return try requestImage(view)
62-
} else {
63-
// Default: Load & set image.
64-
guard let view = view as? NativeImageView else { return }
65-
try loadImage()
66-
.then { image in
67-
guard let image = image as? NativeImage else { return }
68-
DispatchQueue.runOnMain {
69-
view.imageView.image = image.uiImage
70-
}
44+
guard let view = view as? NativeImageView else { return }
45+
try loadImage()
46+
.then { image in
47+
guard let image = image as? NativeImage else { return }
48+
DispatchQueue.runOnMain {
49+
view.imageView.image = image.uiImage
7150
}
72-
}
51+
}
7352
}
7453

7554
func dropImage(forView view: any HybridNitroImageViewSpec) throws {
76-
if let dropImage {
77-
// Custom dropImage(...) func supplied
78-
return try dropImage(view)
79-
} else {
80-
// Default: Set image to nil
81-
guard let view = view as? NativeImageView else { return }
82-
DispatchQueue.runOnMain {
83-
view.imageView.image = nil
84-
}
55+
guard let view = view as? NativeImageView else { return }
56+
DispatchQueue.runOnMain {
57+
view.imageView.image = nil
8558
}
8659
}
8760
}

packages/react-native-nitro-image/src/OptionalWebLoader.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
// biome-ignore lint/suspicious/noTsIgnore: Type Compilation is a race-condition
22
// @ts-ignore
33
type WebImagesType = typeof import("react-native-nitro-web-image")["WebImages"];
4-
type OptionalWebImagesType = Pick<WebImagesType, 'createWebImageLoader' | 'loadFromURLAsync'>
4+
type OptionalWebImagesType = Pick<
5+
WebImagesType,
6+
"createWebImageLoader" | "loadFromURLAsync"
7+
>;
58

69
let createWebImageLoader: WebImagesType["createWebImageLoader"] = () => {
710
throw new Error(
@@ -27,4 +30,7 @@ try {
2730
// react-native-nitro-web-image is not installed, so only local images are supported.
2831
}
2932

30-
export const OptionalWebImages: OptionalWebImagesType = { createWebImageLoader, loadFromURLAsync };
33+
export const OptionalWebImages: OptionalWebImagesType = {
34+
createWebImageLoader,
35+
loadFromURLAsync,
36+
};

0 commit comments

Comments
 (0)