Skip to content

Commit 8060669

Browse files
committed
Address feedback items
1 parent 2c0b101 commit 8060669

File tree

7 files changed

+53
-105
lines changed

7 files changed

+53
-105
lines changed

android/Gutenberg/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ dependencies {
6464
implementation(libs.okhttp)
6565

6666
testImplementation(libs.junit)
67+
testImplementation(libs.kotlinx.coroutines.test)
6768
testImplementation(libs.mockito.core)
6869
testImplementation(libs.mockito.kotlin)
6970
testImplementation(libs.robolectric)

android/Gutenberg/src/main/java/org/wordpress/gutenberg/GutenbergView.kt

Lines changed: 31 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ import android.webkit.WebResourceResponse
2222
import android.webkit.WebStorage
2323
import android.webkit.WebView
2424
import android.webkit.WebViewClient
25+
import androidx.lifecycle.coroutineScope
26+
import androidx.lifecycle.findViewTreeLifecycleOwner
27+
import androidx.lifecycle.lifecycleScope
2528
import androidx.webkit.WebViewAssetLoader
2629
import androidx.webkit.WebViewAssetLoader.AssetsPathHandler
2730
import kotlinx.coroutines.CoroutineScope
@@ -47,8 +50,8 @@ class GutenbergView : WebView {
4750
private var assetLoader = WebViewAssetLoader.Builder()
4851
.addPathHandler("/assets/", AssetsPathHandler(this.context))
4952
.build()
50-
private var configuration: EditorConfiguration = EditorConfiguration.bundled()
51-
private var dependencies: EditorDependencies? = null
53+
private val configuration: EditorConfiguration
54+
private lateinit var dependencies: EditorDependencies
5255

5356
private val handler = Handler(Looper.getMainLooper())
5457
var filePathCallback: ValueCallback<Array<Uri?>?>? = null
@@ -68,14 +71,14 @@ class GutenbergView : WebView {
6871
private var networkRequestListener: NetworkRequestListener? = null
6972
private var loadingListener: EditorLoadingListener? = null
7073

71-
private val coroutineScope = CoroutineScope(Dispatchers.Main + SupervisorJob())
72-
7374
/**
7475
* Stores the contextId from the most recent openMediaLibrary call
7576
* to pass back to JavaScript when media is selected
7677
*/
7778
private var currentMediaContextId: String? = null
7879

80+
private val coroutineScope: CoroutineScope
81+
7982
var textEditorEnabled: Boolean = false
8083
set(value) {
8184
field = value
@@ -129,16 +132,23 @@ class GutenbergView : WebView {
129132
loadingListener = listener
130133
}
131134

132-
constructor(context: Context) : super(context)
133-
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
134-
constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(
135-
context,
136-
attrs,
137-
defStyle
138-
)
135+
constructor(configuration: EditorConfiguration, dependencies: EditorDependencies?, coroutineScope: CoroutineScope, context: Context) : super(context) {
136+
this.configuration = configuration
137+
this.coroutineScope = coroutineScope
138+
139+
if (dependencies != null) {
140+
this.dependencies = dependencies
141+
142+
// FAST PATH: Dependencies were provided - load immediately
143+
loadEditor(dependencies)
144+
} else {
145+
// ASYNC FLOW: No dependencies - fetch them asynchronously
146+
prepareAndLoadEditor()
147+
}
148+
}
139149

140150
@SuppressLint("SetJavaScriptEnabled") // Without JavaScript we have no Gutenberg
141-
fun initializeWebView() {
151+
private fun initializeWebView() {
142152
this.settings.javaScriptCanOpenWindowsAutomatically = true
143153
this.settings.javaScriptEnabled = true
144154
this.settings.domStorageEnabled = true
@@ -272,38 +282,6 @@ class GutenbergView : WebView {
272282
}
273283
}
274284

275-
/**
276-
* Starts the editor with the given configuration and optional dependencies.
277-
*
278-
* ## Loading Flows
279-
*
280-
* **When dependencies are provided (Fast Path):**
281-
* The editor loads immediately using the pre-fetched dependencies.
282-
* Only `onDependencyLoadingFinished()` and `onEditorReady()` callbacks are invoked.
283-
*
284-
* **When dependencies are null (Async Flow):**
285-
* Dependencies are fetched asynchronously with progress reporting.
286-
* All loading callbacks are invoked in order:
287-
* 1. `onDependencyLoadingStarted()`
288-
* 2. `onDependencyLoadingProgress()` (multiple times)
289-
* 3. `onDependencyLoadingFinished()`
290-
* 4. `onEditorReady()`
291-
*
292-
* @param configuration The editor configuration.
293-
* @param dependencies Pre-fetched dependencies, or null to fetch them asynchronously.
294-
*/
295-
fun start(configuration: EditorConfiguration, dependencies: EditorDependencies? = null) {
296-
this.configuration = configuration
297-
298-
if (dependencies != null) {
299-
// FAST PATH: Dependencies were provided - load immediately
300-
loadEditor(dependencies)
301-
} else {
302-
// ASYNC FLOW: No dependencies - fetch them asynchronously
303-
prepareAndLoadEditor()
304-
}
305-
}
306-
307285
/**
308286
* Fetches all required dependencies and then loads the editor.
309287
*
@@ -312,17 +290,25 @@ class GutenbergView : WebView {
312290
private fun prepareAndLoadEditor() {
313291
loadingListener?.onDependencyLoadingStarted()
314292

293+
Log.i("GutenbergView", "Fetching dependencies...")
294+
315295
coroutineScope.launch {
296+
Log.i("GutenbergView", "In coroutine scope")
297+
Log.i("GutenbergView", "Fetching dependencies in IO context")
316298
try {
317299
val editorService = EditorService.create(
318300
context = context,
319301
configuration = configuration
320302
)
321-
303+
Log.i("GutenbergView", "Created editor service")
322304
val fetchedDependencies = editorService.prepare { progress ->
323305
loadingListener?.onDependencyLoadingProgress(progress)
306+
307+
Log.i("GutenbergView", "Progress: $progress")
324308
}
325309

310+
Log.i("GutenbergView", "Finished fetching dependencies")
311+
326312
// Store dependencies and load the editor
327313
loadEditor(fetchedDependencies)
328314
} catch (e: Exception) {
@@ -759,7 +745,6 @@ class GutenbergView : WebView {
759745

760746
override fun onDetachedFromWindow() {
761747
super.onDetachedFromWindow()
762-
coroutineScope.cancel()
763748
clearConfig()
764749
this.stopLoading()
765750
FileCache.clearCache(context)
@@ -785,40 +770,6 @@ class GutenbergView : WebView {
785770
private var warmupRunnable: Runnable? = null
786771
private var warmupWebView: GutenbergView? = null
787772

788-
/**
789-
* Warmup the editor by preloading assets in a temporary WebView.
790-
* This pre-caches assets to improve editor launch speed.
791-
*/
792-
@JvmStatic
793-
fun warmup(context: Context, configuration: EditorConfiguration) {
794-
// Cancel any existing warmup
795-
cancelWarmup()
796-
797-
// Create dedicated warmup WebView
798-
val webView = GutenbergView(context)
799-
webView.initializeWebView()
800-
webView.start(configuration, EditorDependencies.empty)
801-
warmupWebView = webView
802-
803-
// Schedule cleanup after assets are loaded
804-
warmupHandler = Handler(Looper.getMainLooper())
805-
warmupRunnable = Runnable {
806-
cleanupWarmup()
807-
}
808-
warmupHandler?.postDelayed(warmupRunnable!!, ASSET_LOADING_TIMEOUT_MS)
809-
}
810-
811-
/**
812-
* Cancel any pending warmup and clean up resources.
813-
*/
814-
@JvmStatic
815-
fun cancelWarmup() {
816-
warmupRunnable?.let { runnable ->
817-
warmupHandler?.removeCallbacks(runnable)
818-
}
819-
cleanupWarmup()
820-
}
821-
822773
/**
823774
* Clean up warmup resources.
824775
*/
@@ -832,21 +783,6 @@ class GutenbergView : WebView {
832783
warmupHandler = null
833784
warmupRunnable = null
834785
}
835-
836-
/**
837-
* Create a new GutenbergView for the editor.
838-
* Cancels any pending warmup to free resources.
839-
*/
840-
@JvmStatic
841-
fun createForEditor(context: Context): GutenbergView {
842-
// Cancel any pending warmup to free resources
843-
cancelWarmup()
844-
845-
// Create fresh WebView for editor
846-
val webView = GutenbergView(context)
847-
webView.initializeWebView()
848-
return webView
849-
}
850786
}
851787
}
852788

android/Gutenberg/src/main/java/org/wordpress/gutenberg/model/EditorAssetBundle.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ data class EditorAssetBundle(
8585
val empty = EditorAssetBundle(
8686
manifest = LocalEditorAssetManifest.empty,
8787
downloadDate = Date(),
88-
bundleRoot = File(System.getProperty("java.io.tmpdir"))
88+
bundleRoot = File("/tmp/empty-bundle")
8989
)
9090

9191
/**

android/Gutenberg/src/main/java/org/wordpress/gutenberg/model/EditorDependenciesSerializer.kt

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,7 @@ object EditorDependenciesSerializer {
7979
* @param filePath The absolute path to the serialized dependencies file.
8080
* @return The deserialized dependencies, or `null` if the file doesn't exist or is invalid.
8181
*/
82-
fun readFromDisk(filePath: String?): EditorDependencies? {
83-
if (filePath == null) return null
84-
82+
fun readFromDisk(filePath: String): EditorDependencies? {
8583
val file = File(filePath)
8684
if (!file.exists()) return null
8785

android/Gutenberg/src/test/java/org/wordpress/gutenberg/GutenbergViewTest.kt

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@ import android.os.Looper
66
import android.webkit.ValueCallback
77
import android.webkit.WebChromeClient
88
import android.webkit.WebView
9+
import kotlinx.coroutines.test.TestScope
910
import org.junit.Before
1011
import org.junit.Test
12+
import org.junit.Rule
1113
import org.junit.runner.RunWith
1214
import org.mockito.Mock
1315
import org.mockito.Mockito.`when`
@@ -20,6 +22,8 @@ import org.junit.Assert.assertEquals
2022
import org.junit.Assert.assertTrue
2123
import java.util.concurrent.CountDownLatch
2224
import java.util.concurrent.TimeUnit
25+
import org.wordpress.gutenberg.model.EditorConfiguration
26+
import org.wordpress.gutenberg.model.EditorDependencies
2327

2428
@RunWith(RobolectricTestRunner::class)
2529
@Config(sdk = [28], manifest = Config.NONE)
@@ -35,11 +39,18 @@ class GutenbergViewTest {
3539

3640
private lateinit var gutenbergView: GutenbergView
3741

42+
val testScope = TestScope() // Creates a StandardTestDispatcher
43+
3844
@Before
3945
fun setup() {
4046
MockitoAnnotations.openMocks(this)
41-
gutenbergView = GutenbergView(RuntimeEnvironment.getApplication())
42-
gutenbergView.initializeWebView()
47+
48+
gutenbergView = GutenbergView(
49+
EditorConfiguration.bundled(),
50+
EditorDependencies.empty,
51+
testScope,
52+
RuntimeEnvironment.getApplication()
53+
)
4354
}
4455

4556
@Test

android/app/src/main/java/com/example/gutenbergkit/EditorActivity.kt

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ import androidx.compose.material3.Text
3434
import androidx.compose.material3.TextButton
3535
import androidx.compose.material3.TopAppBar
3636
import androidx.compose.runtime.Composable
37-
import androidx.compose.runtime.MutableState
3837
import androidx.compose.runtime.getValue
3938
import androidx.compose.runtime.mutableFloatStateOf
4039
import androidx.compose.runtime.mutableStateOf
@@ -47,6 +46,7 @@ import androidx.compose.ui.unit.dp
4746
import androidx.compose.ui.viewinterop.AndroidView
4847
import androidx.lifecycle.lifecycleScope
4948
import com.example.gutenbergkit.ui.theme.AppTheme
49+
import kotlinx.coroutines.CoroutineScope
5050
import kotlinx.coroutines.launch
5151
import org.wordpress.gutenberg.model.EditorConfiguration
5252
import org.wordpress.gutenberg.GutenbergView
@@ -97,13 +97,14 @@ class EditorActivity : ComponentActivity() {
9797

9898
// Read dependencies from disk if a file path was provided
9999
val dependenciesPath = intent.getStringExtra(EXTRA_DEPENDENCIES_PATH)
100-
val dependencies = EditorDependenciesSerializer.readFromDisk(dependenciesPath)
100+
val dependencies = dependenciesPath?.let { EditorDependenciesSerializer.readFromDisk(it) }
101101

102102
setContent {
103103
AppTheme {
104104
EditorScreen(
105105
configuration = configuration,
106106
dependencies = dependencies,
107+
coroutineScope = this.lifecycleScope,
107108
onClose = { finish() },
108109
onGutenbergViewCreated = { view ->
109110
gutenbergView = view
@@ -140,6 +141,7 @@ enum class EditorLoadingState {
140141
fun EditorScreen(
141142
configuration: EditorConfiguration,
142143
dependencies: EditorDependencies? = null,
144+
coroutineScope: CoroutineScope,
143145
onClose: () -> Unit,
144146
onGutenbergViewCreated: (GutenbergView) -> Unit = {}
145147
) {
@@ -256,7 +258,7 @@ fun EditorScreen(
256258
) { innerPadding ->
257259
AndroidView(
258260
factory = { context ->
259-
GutenbergView(context).apply {
261+
GutenbergView(configuration, dependencies, coroutineScope, context).apply {
260262
gutenbergViewRef = this
261263
setModalDialogStateListener(object : GutenbergView.ModalDialogStateListener {
262264
override fun onModalDialogOpened(dialogType: String) {
@@ -326,7 +328,6 @@ fun EditorScreen(
326328
loadingError = error.message ?: "Unknown error"
327329
}
328330
})
329-
start(configuration, dependencies)
330331
onGutenbergViewCreated(this)
331332
}
332333
},

android/gradle/libs.versions.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ mockito-core = { group = "org.mockito", name = "mockito-core", version.ref = "mo
3737
mockito-kotlin = { group = "org.mockito.kotlin", name = "mockito-kotlin", version.ref = "mockito" }
3838
robolectric = { group = "org.robolectric", name = "robolectric", version.ref = "robolectric" }
3939
kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "kotlinx-coroutines" }
40+
kotlinx-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "kotlinx-coroutines" }
4041
kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinx-serialization" }
4142
androidx-recyclerview = { group = "androidx.recyclerview", name = "recyclerview", version.ref = "androidx-recyclerview" }
4243
wordpress-rs-android = { group = "rs.wordpress.api", name = "android", version.ref = "wordpress-rs" }

0 commit comments

Comments
 (0)