Skip to content

MOBILE-324: added embedded block - #751

Merged
sergeysozinov merged 8 commits into
mission/storiesfrom
feature/MOBILE-324
Aug 11, 2026
Merged

MOBILE-324: added embedded block#751
sergeysozinov merged 8 commits into
mission/storiesfrom
feature/MOBILE-324

Conversation

@sergeysozinov

Copy link
Copy Markdown
Collaborator

sozinov added 4 commits August 10, 2026 13:17
Stands in for the backend contract (MOBILE-344) until it lands: the
inlineBlocks config section, the block page bridge name and the page
message protocol.

Everything here is prefixed Temp on purpose — it is replaced wholesale
once the real contract arrives and the block moves to the shared JS
bridge, so nothing else should grow a dependency on these shapes.
Injects a hardcoded inlineBlocks section into the fetched mobile config
and serves a mock feed page with switchable scenarios (success, empty,
error, slow), so the block can be driven end to end before the backend
sends anything.

MUST NOT REACH develop. The injection is guarded by BuildConfig.DEBUG,
so a release build never substitutes the staging page into a host app's
config — verified on a release build.
A host marks a place by placeSystemName and gives it a fixed height; the
mobile config decides what goes into it. MindboxEmbeddedBlockView is the
whole public surface, with a Compose wrapper in the new
mindbox-embedded-compose module.

The block owns its behavior: visible while loading and while showing
content, GONE when the place ends up without content unless the host set
an error view. The listener only observes.

Inside, the controller decides when to resolve, when to drop and when to
give the page its 7s budget; the factory turns the config into one of
three outcomes, telling apart 'nothing here' from 'config has not arrived
yet' so a slow start-up never collapses a block; the webview layer keeps
the untrusted page contained — https only, no navigation, a rate-limited
bridge, and a dead renderer that no longer takes the host app down.

Shared SDK code touched:
- SessionStorageManager: listeners can now unsubscribe, and the list is
  copy-on-write since blocks subscribe from the main thread while the
  keepalive timer notifies from another.
- MindboxEventManager/Event: the embeddedPlaceRequested signal.
- InAppEventManagerImpl: that signal reaches the in-app pipeline, so a
  place can be filled by targeting once embedded in-apps exist.
- Constants: the 7s WebView readiness budget, shared with the in-app
  holder that already used the same number.
128 tests over the block: the controller's decisions (resolve once,
reload on a new session, wait for a config that has not arrived, budget
only the page), the view's states and callbacks, the provider's reading
of page heights, and the page hardening (https, navigation, bridge rate
limit, dead renderer).

Robolectric never raises a window out of GONE and the block starts its
content only on a visible window, so the tests dispatch that callback
themselves through Robolectric's own helper.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds embedded Mindbox blocks for Android Views and Compose, including WebView rendering, lifecycle handling, configuration lookup, and tests.

Changes:

  • Introduces embedded-block APIs and Compose integration.
  • Adds WebView messaging, targeting events, placeholders, and lifecycle management.
  • Adds extensive unit tests and temporary mock configuration/content.

Reviewed changes

Copilot reviewed 47 out of 48 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
.editorconfig Allows PascalCase composables.
build.gradle Adds Compose module coverage.
gradle/libs.versions.toml Adds Compose and lifecycle dependencies.
mindbox-embedded-compose/build.gradle Configures the Compose module.
mindbox-embedded-compose/gradle.properties Defines artifact metadata.
mindbox-embedded-compose/proguard-rules.pro Adds module ProGuard placeholder.
mindbox-embedded-compose/src/main/java/cloud/mindbox/mobile_sdk/embedded/compose/MindboxEmbeddedBlock.kt Implements the Compose wrapper.
mindbox-embedded-compose/src/test/java/cloud/mindbox/mobile_sdk/embedded/compose/MindboxEmbeddedBlockTest.kt Tests Compose behavior.
mindbox-embedded-compose/src/test/java/cloud/mindbox/mobile_sdk/embedded/compose/WindowVisibility.kt Adds Robolectric visibility helper.
modulesCommon.gradle Enables JVM interface defaults.
sdk/build.gradle Exposes Compose artifact and lifecycle runtime.
sdk/consumer-rules.pro Preserves JavaScript bridge methods.
sdk/src/main/java/cloud/mindbox/mobile_sdk/embedded/EmbeddedBlockContentController.kt Coordinates block resolution and lifecycle.
sdk/src/main/java/cloud/mindbox/mobile_sdk/embedded/EmbeddedBlockContentFactory.kt Builds block content from configuration.
sdk/src/main/java/cloud/mindbox/mobile_sdk/embedded/EmbeddedBlockDefaultViews.kt Implements the loading placeholder.
sdk/src/main/java/cloud/mindbox/mobile_sdk/embedded/EmbeddedBlockState.kt Defines block states.
sdk/src/main/java/cloud/mindbox/mobile_sdk/embedded/EmbeddedContentProvider.kt Defines the content-provider contract.
sdk/src/main/java/cloud/mindbox/mobile_sdk/embedded/EmbeddedContentResolution.kt Defines resolution outcomes.
sdk/src/main/java/cloud/mindbox/mobile_sdk/embedded/MindboxEmbeddedBlockListener.kt Adds the public listener API.
sdk/src/main/java/cloud/mindbox/mobile_sdk/embedded/MindboxEmbeddedBlockView.kt Implements the embedded block View.
sdk/src/main/java/cloud/mindbox/mobile_sdk/embedded/TempEmbeddedBlocksConfig.kt Parses temporary block configuration.
sdk/src/main/java/cloud/mindbox/mobile_sdk/embedded/mock/TempEmbeddedBlocksMockConfigSection.kt Injects temporary mock placements.
sdk/src/main/java/cloud/mindbox/mobile_sdk/embedded/mock/TempMindboxStoriesFeedMock.kt Exposes mock scenarios.
sdk/src/main/java/cloud/mindbox/mobile_sdk/embedded/mock/TempStoriesFeedMockPage.kt Provides mock feed HTML.
sdk/src/main/java/cloud/mindbox/mobile_sdk/embedded/webview/EmbeddedBlockPage.kt Defines the page abstraction.
sdk/src/main/java/cloud/mindbox/mobile_sdk/embedded/webview/EmbeddedBlockWebViewPage.kt Implements secure WebView rendering.
sdk/src/main/java/cloud/mindbox/mobile_sdk/embedded/webview/EmbeddedBlockWebViewProvider.kt Maps page events to block states.
sdk/src/main/java/cloud/mindbox/mobile_sdk/embedded/webview/TempEmbeddedBlockPageContract.kt Defines bridge constants.
sdk/src/main/java/cloud/mindbox/mobile_sdk/embedded/webview/TempEmbeddedBlockPageMessage.kt Parses bridge messages.
sdk/src/main/java/cloud/mindbox/mobile_sdk/inapp/data/managers/SessionStorageManager.kt Adds removable thread-safe listeners.
sdk/src/main/java/cloud/mindbox/mobile_sdk/inapp/data/repositories/MobileConfigRepositoryImpl.kt Injects temporary embedded configuration.
sdk/src/main/java/cloud/mindbox/mobile_sdk/inapp/domain/InAppEventManagerImpl.kt Accepts embedded-place events.
sdk/src/main/java/cloud/mindbox/mobile_sdk/inapp/presentation/view/WebViewInappViewHolder.kt Reuses the shared WebView timeout.
sdk/src/main/java/cloud/mindbox/mobile_sdk/managers/MindboxEventManager.kt Emits embedded-place requests.
sdk/src/main/java/cloud/mindbox/mobile_sdk/models/Event.kt Defines the embedded request event.
sdk/src/main/java/cloud/mindbox/mobile_sdk/utils/Constants.kt Centralizes WebView timeout.
sdk/src/main/res/values-night/colors.xml Adds dark placeholder color.
sdk/src/main/res/values/attrs.xml Adds the place-name XML attribute.
sdk/src/main/res/values/colors.xml Adds light placeholder color.
sdk/src/test/java/cloud/mindbox/mobile_sdk/embedded/EmbeddedBlockContentControllerTest.kt Tests controller behavior.
sdk/src/test/java/cloud/mindbox/mobile_sdk/embedded/EmbeddedBlockContentFactoryTest.kt Tests configuration resolution.
sdk/src/test/java/cloud/mindbox/mobile_sdk/embedded/MindboxEmbeddedBlockViewLookupTest.kt Tests public configuration lookup.
sdk/src/test/java/cloud/mindbox/mobile_sdk/embedded/MindboxEmbeddedBlockViewTest.kt Tests View behavior and lifecycle.
sdk/src/test/java/cloud/mindbox/mobile_sdk/embedded/WindowVisibility.kt Adds SDK Robolectric helper.
sdk/src/test/java/cloud/mindbox/mobile_sdk/embedded/webview/EmbeddedBlockWebViewPageTest.kt Tests WebView security and messaging.
sdk/src/test/java/cloud/mindbox/mobile_sdk/embedded/webview/EmbeddedBlockWebViewProviderTest.kt Tests provider state handling.
sdk/src/test/java/cloud/mindbox/mobile_sdk/inapp/domain/InAppEventManagerTest.kt Tests embedded event validation.
settings.gradle Registers the Compose module.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 47 out of 48 changed files in this pull request and generated no new comments.

Suppressed comments (2)

sdk/src/main/java/cloud/mindbox/mobile_sdk/embedded/webview/EmbeddedBlockWebViewProvider.kt:42

  • A failed page is paused when the failure is detected, but every later start() resumes it before replaying Failed. After a detach/reattach this lets the broken page run JS and emit bridge messages again; for example, a later heightChanged can move the provider back to Ready, despite the documented behavior that a page failure remains failed until a new session reloads it. Keep failed pages paused when replaying this terminal state.
        if (isLoaded) {
            page.resume()
            report(lastState)

sdk/src/main/java/cloud/mindbox/mobile_sdk/embedded/MindboxEmbeddedBlockView.kt:145

  • Clearing an error view while the block is already in Empty/Failed only clears this field; the old error child remains attached and the block remains visible. The Compose wrapper now calls this setter on a non-null→null slot update, so that child recomposes to blank content and leaves a permanent layout hole. When removing an existing error view in a no-content state, clear the shown child and reapply the default collapsed visibility (while preserving the intentional behavior that adding an error view after collapse waits for the next outcome).
    public fun setErrorView(view: View?) {
        errorView = view
    }

@justSmK justSmK left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ревью в паре с iOS-стеком (mindbox-cloud/ios-sdk#751, #754, #755, #756) — смотрел обе платформы, чтобы поймать расхождения. Проверял состояние ветки на a7e7383c, замечания Copilot, закрытые в «follow review», не повторяю.

Сначала о хорошем

Веб-слой здесь заметно крепче, чем на iOS, и это стоит зафиксировать как эталон, а не как местную деталь:

  • блокировка навигации на обеих перегрузках shouldOverrideUrlLoading (API 21+);
  • rate limit моста 30 сообщений/с и лимит размера сообщения 16 КБ;
  • санитизация логов страницы (обрезка + вычистка переводов строк);
  • onRenderProcessGone — без него дохлый рендерер роняет приложение хоста на API 26+;
  • https-only с разбором URI и проверкой userInfo, MIXED_CONTENT_NEVER_ALLOW, allowFileAccess=false;
  • proguard-правило для @JavascriptInterface.

На iOS из этого нет ничего — там даже decidePolicyFor не реализован, и PR это признаёт. Завёл это как отдельный разговор на iOS-стороне.

Плюс ожидание конфига через NotReadyYet + подписка на inAppConfigFlow, перезагрузка по новой сессии и BuildConfig.DEBUG-гард на инъекции мока — всего этого на iOS тоже нет, там хардкод прод-URL вообще ничем не закрыт. И 134 теста без единого sleep (на iOS 16 реальных снов суммарно ~5.5 секунды).

Что вижу главным

Три пункта DoD не закрыты, и ровно те же три не закрыты на iOS — значит это не локальный недосмотр, а решение, которое надо принять один раз на обе платформы: делегат «загрузка», дефолтный экран ошибки, «одинаковые ID → тот же объект». Подробности инлайном.

Отдельно: раздел «Имплементация» в задаче описывает EmbeddedBlockContentRegistry, DeferredContentProvider, StoriesEmbeddedContentProvider и EmbeddedBlockDefaultViews.error — в ветке этих классов нет. Описание задачи стоит привести в соответствие с кодом, иначе приёмка пойдёт по несуществующему плану.

Разное поведение при общем веб-контракте

Контракт со фронтом один, а платформы трактуют его по-разному — это надо свести до того, как фронт начнёт писать страницы:

Android iOS
heightChanged обрабатывается как Ready игнорируется полностью
ready с нулевой высотой Empty («таргетинг ничего не выбрал») Failed («сломанная вёрстка»)
бюджет таймаута сбрасывается в полный на каждое появление накапливает потраченное, пауза в фоне
аналитика шлёт embeddedPlaceRequested не шлёт ничего

Мелочь, о которой уже договорились

Фолбэк на мок-страницу при pageUrl == null в EmbeddedBlockContentFactory ещё в ветке — на комментарий Copilot ответили «Да выпилим моки», просто чтобы не потерялось.

Comment thread modulesCommon.gradle Outdated
@sergeysozinov
sergeysozinov merged commit 1c1746e into mission/stories Aug 11, 2026
6 checks passed
@sergeysozinov
sergeysozinov deleted the feature/MOBILE-324 branch August 11, 2026 15:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants