Migration Guide: 2.x → 3.x

🚧

Deprecated in 3.0.0

Existing OWUIFlows and OWUIViews code keeps working in 3.0.0 but produces deprecation warnings at compile time. Migrate to OWUIComponents to clear the warnings.

For the full Components API reference, see the Components page.


Version 3.0.0 introduces three additions to the OpenWeb Android SDK:

  • OWUIComponents — a unified API that replaces both OWUIFlows and OWUIViews
  • Jetpack Compose support — PreConversation and Conversation composables in the new spotim-compose module
  • Per-element font customization via OWCustomizationElements

Accessing the New Components API

Before (2.x):

val flows = OpenWeb.manager.ui.flows
val views = OpenWeb.manager.ui.views

After (3.x):

val components = OpenWeb.manager.ui.components

Flows.fragments → Components

Before (2.x)After (3.x)
flows.fragments.getPreConversation(postId, …)components.getPreConversation(postId, …)
flows.fragments.getConversation(postId, …)components.getConversation(postId, …)

getPreConversation (fragment embed)

Before:

flows.fragments.getPreConversation(
    postId = "POST_ID",
    articleSettings = OWArticleSettings(),
    additionalSettings = OWAdditionalSettings(),
    flowActionsCallback = null,
    callback = object : SpotCallback<Fragment>() {
        override fun onSuccess(fragment: Fragment) {
            supportFragmentManager.beginTransaction()
                .replace(R.id.container, fragment)
                .commit()
        }
        override fun onFailure(exception: SpotException) {
            // handle error
        }
    }
)

After:

components.getPreConversation(
    postId = "POST_ID",
    articleSettings = OWArticleSettings(),
    conversationNavigation = OWConversationNavigationMode.ConversationFullScreen(),
    additionalSettings = OWAdditionalSettings(),
    actionCallbacks = null,
    completion = object : SpotCallback<Fragment>() {
        override fun onSuccess(fragment: Fragment) {
            supportFragmentManager.beginTransaction()
                .replace(R.id.container, fragment)
                .commit()
        }
        override fun onFailure(exception: SpotException) {
            // handle error
        }
    }
)

getConversation (fragment embed)

Before:

flows.fragments.getConversation(
    postId = "POST_ID",
    articleSettings = OWArticleSettings(),
    additionalSettings = OWAdditionalSettings(),
    flowActionsCallback = null,
    callback = object : SpotCallback<Fragment>() {
        override fun onSuccess(fragment: Fragment) {
            supportFragmentManager.beginTransaction()
                .replace(R.id.container, fragment)
                .commit()
        }
        override fun onFailure(exception: SpotException) {
            // handle error
        }
    }
)

After:

components.getConversation(
    postId = "POST_ID",
    articleSettings = OWArticleSettings(),
    route = null,
    additionalSettings = OWAdditionalSettings(),
    actionCallbacks = null,
    completion = object : SpotCallback<Fragment>() {
        override fun onSuccess(fragment: Fragment) {
            supportFragmentManager.beginTransaction()
                .replace(R.id.container, fragment)
                .commit()
        }
        override fun onFailure(exception: SpotException) {
            // handle error
        }
    }
)

Flows.intents → openConversation()

Before (2.x)After (3.x)
flows.intents.getConversation(postId, …)components.openConversation(postId, …)
flows.intents.getCommentThread(postId, commentId, …)components.openConversation(…, route = OWConversationRoute.OWCommentThreadRoute(commentId))
flows.intents.getCommentCreation(postId, …)components.openConversation(…, route = OWConversationRoute.OWCommentCreationRoute(type))

Conversation intent

Before:

flows.intents.getConversation(
    postId = "POST_ID",
    articleSettings = OWArticleSettings(),
    additionalSettings = OWAdditionalSettings(),
    flowActionsCallback = null,
    callback = object : SpotCallback<Intent>() {
        override fun onSuccess(intent: Intent) { startActivity(intent) }
        override fun onFailure(exception: SpotException) { /* handle error */ }
    }
)

After:

components.openConversation(
    postId = "POST_ID",
    articleSettings = OWArticleSettings(),
    additionalSettings = OWAdditionalSettings(),
    actionCallbacks = null
)

Comment thread intent

Before:

flows.intents.getCommentThread(
    postId = "POST_ID",
    commentId = "COMMENT_ID",
    articleSettings = OWArticleSettings(),
    additionalSettings = OWAdditionalSettings(),
    flowActionsCallback = null,
    callback = object : SpotCallback<Intent>() {
        override fun onSuccess(intent: Intent) { startActivity(intent) }
        override fun onFailure(exception: SpotException) { /* handle error */ }
    }
)

After:

components.openConversation(
    postId = "POST_ID",
    route = OWConversationRoute.OWCommentThreadRoute(commentId = "COMMENT_ID"),
    additionalSettings = OWAdditionalSettings(),
    actionCallbacks = null
)

Comment creation intent

Before:

flows.intents.getCommentCreation(
    postId = "POST_ID",
    articleSettings = OWArticleSettings(),
    additionalSettings = OWAdditionalSettings(),
    flowActionsCallback = null,
    callback = object : SpotCallback<Intent>() {
        override fun onSuccess(intent: Intent) { startActivity(intent) }
        override fun onFailure(exception: SpotException) { /* handle error */ }
    }
)

After:

components.openConversation(
    postId = "POST_ID",
    route = OWConversationRoute.OWCommentCreationRoute(type = OWCommentCreationType.Comment),
    additionalSettings = OWAdditionalSettings(),
    actionCallbacks = null
)

Views → Components

Before (2.x)After (3.x)
views.getPreConversation(postId, …)components.getPreConversation(postId, …)
views.getConversation(postId, …)components.getConversation(postId, …)
views.getCommentThread(commentId, postId, …)components.getConversation(…, route = OWConversationRoute.OWCommentThreadRoute(commentId))
views.getCommentCreation(postId, …, commentCreationType, …)components.getConversation(…, route = OWConversationRoute.OWCommentCreationRoute(type))
views.getReportReasons(…)Removed — handled internally by the SDK

getPreConversation

Before:

views.getPreConversation(
    postId = "POST_ID",
    articleSettings = OWArticleSettings(),
    preConversationSettings = OWPreConversationSettings(),
    viewActionsCallbacks = null,
    callback = object : SpotCallback<Fragment>() {
        override fun onSuccess(fragment: Fragment) {
            supportFragmentManager.beginTransaction()
                .replace(R.id.container, fragment)
                .commit()
        }
        override fun onFailure(exception: SpotException) { /* handle error */ }
    }
)

After:

components.getPreConversation(
    postId = "POST_ID",
    articleSettings = OWArticleSettings(),
    conversationNavigation = OWConversationNavigationMode.ConversationFullScreen(),
    additionalSettings = OWAdditionalSettings(),
    actionCallbacks = null,
    completion = object : SpotCallback<Fragment>() {
        override fun onSuccess(fragment: Fragment) {
            supportFragmentManager.beginTransaction()
                .replace(R.id.container, fragment)
                .commit()
        }
        override fun onFailure(exception: SpotException) { /* handle error */ }
    }
)

getConversation

Before:

views.getConversation(
    postId = "POST_ID",
    articleSettings = OWArticleSettings(),
    conversationSettings = OWConversationSettings(),
    viewActionsCallbacks = null,
    callback = object : SpotCallback<Fragment>() {
        override fun onSuccess(fragment: Fragment) {
            supportFragmentManager.beginTransaction()
                .replace(R.id.container, fragment)
                .commit()
        }
        override fun onFailure(exception: SpotException) { /* handle error */ }
    }
)

After:

components.getConversation(
    postId = "POST_ID",
    articleSettings = OWArticleSettings(),
    route = null,
    additionalSettings = OWAdditionalSettings(),
    actionCallbacks = null,
    completion = object : SpotCallback<Fragment>() {
        override fun onSuccess(fragment: Fragment) {
            supportFragmentManager.beginTransaction()
                .replace(R.id.container, fragment)
                .commit()
        }
        override fun onFailure(exception: SpotException) { /* handle error */ }
    }
)

getCommentThread

Before:

views.getCommentThread(
    commentId = "COMMENT_ID",
    postId = "POST_ID",
    articleSettings = OWArticleSettings(),
    commentThreadSettings = OWCommentThreadSettings(),
    viewActionsCallbacks = null,
    callback = object : SpotCallback<Fragment>() {
        override fun onSuccess(fragment: Fragment) {
            supportFragmentManager.beginTransaction()
                .replace(R.id.container, fragment)
                .commit()
        }
        override fun onFailure(exception: SpotException) { /* handle error */ }
    }
)

After:

components.getConversation(
    postId = "POST_ID",
    route = OWConversationRoute.OWCommentThreadRoute(commentId = "COMMENT_ID"),
    additionalSettings = OWAdditionalSettings(),
    actionCallbacks = null,
    completion = object : SpotCallback<Fragment>() {
        override fun onSuccess(fragment: Fragment) {
            supportFragmentManager.beginTransaction()
                .replace(R.id.container, fragment)
                .commit()
        }
        override fun onFailure(exception: SpotException) { /* handle error */ }
    }
)

getCommentCreation

Before:

views.getCommentCreation(
    postId = "POST_ID",
    articleSettings = OWArticleSettings(),
    commentCreationType = OWCommentCreationType.Comment,
    commentCreationSettings = OWCommentCreationSettings(),
    viewActionsCallbacks = null,
    callback = object : SpotCallback<Fragment>() {
        override fun onSuccess(fragment: Fragment) {
            supportFragmentManager.beginTransaction()
                .replace(R.id.container, fragment)
                .commit()
        }
        override fun onFailure(exception: SpotException) { /* handle error */ }
    }
)

After:

components.getConversation(
    postId = "POST_ID",
    route = OWConversationRoute.OWCommentCreationRoute(type = OWCommentCreationType.Comment),
    additionalSettings = OWAdditionalSettings(),
    actionCallbacks = null,
    completion = object : SpotCallback<Fragment>() {
        override fun onSuccess(fragment: Fragment) {
            supportFragmentManager.beginTransaction()
                .replace(R.id.container, fragment)
                .commit()
        }
        override fun onFailure(exception: SpotException) { /* handle error */ }
    }
)

getReportReasons (removed)

views.getReportReasons(...) has no replacement. Report reason selection is now handled internally by the SDK and no longer requires a call site.

Callbacks

OWFlowActionsCallbacks (used in both flows.fragments and flows.intents) remains valid in 3.x via the actionCallbacks parameter on OWUIComponents methods. Rename your existing flowActionsCallback variable to actionCallbacks if desired — the interface is the same.

OWViewActionsCallbacks (used in views.*) is replaced by OWActionCallbacks in the Components API. Update your implementation to conform to OWActionCallbacks:

val actionCallbacks = object : OWActionCallbacks {
    override fun callback(
        type: OWFlowActionCallbackType,
        source: OWFlowSourceType,
        postId: OWPostId
    ) {
        // same logic as before
    }
}

Customizations

3.0.0 introduces OWCustomizationElements, accessed via OpenWeb.manager.ui.customizations.elements. It replaces the OWTheme (customizedTheme), OWCommentActionsCustomizations (commentActions), and setCustomUIDelegate APIs.

Note: The 2.x customization APIs still compile in 3.0.0 — they emit deprecation warnings and are scheduled for removal in 4.0.0. Migrate before upgrading to 4.x. For the full element reference, see the Customization page.

Theme → Elements

Replace customizedTheme (OWTheme) property assignments with the matching elements.<element>.color.

Before (2.x):

OpenWeb.manager.ui.customizations.customizedTheme = OWTheme(
    primaryBackgroundColor = UIColor(0xFFFFFFFF),
    primaryTextColor = UIColor(0xFF000000),
    brandColor = UIColor(0xFFFF5722)
)

After (3.x):

with(OpenWeb.manager.ui.customizations.elements) {
    background.color = UIColor(lightColor = Color.WHITE, darkColor = Color.BLACK)
    commentBody.color = UIColor(lightColor = Color.BLACK, darkColor = Color.WHITE)
    brand.color = UIColor(lightColor = Color.parseColor("#FF5722"), darkColor = Color.parseColor("#FF5722"))
}

Full property mapping (from the SDK's own deprecation hints):

OWTheme property (2.x)elements equivalent (3.x)
primaryBackgroundColorbackground
secondaryBackgroundColoroverlayBackground
tertiaryBackgroundColorcardBackground
primaryTextColorcommentBody
secondaryTextColorsubtitle
tertiaryTextColordetail
primarySeparatorColorsectionDivider
secondarySeparatorColorcontentDivider
tertiarySeparatorColordivider
primaryBorderColorborder
secondaryBorderColorRemoved — no replacement
loaderColorloader
brandColorbrand
skeletonColorskeletonGradientEdge
skeletonShimmeringColorskeletonGradientCenter
voteUpSelectedColorvoteUpSelected
voteUpUnselectedColorvoteUpUnselected
voteDownSelectedColorvoteDownSelected
voteDownUnselectedColorvoteDownUnselected
statusBarColorRemoved — no replacement
navigationBarColorRemoved — no replacement

Comment Actions → elements.commentActions

The commentActions enum-based API is replaced by direct color and font control on elements.commentActions.

Before (2.x):

OpenWeb.manager.ui.customizations.commentActions = OWCommentActionsCustomizations(
    commentActionsButtonsColor = CommentActionsButtonsColor.BRAND_COLOR,
    commentActionsButtonsFont = CommentActionsButtonsFont.SEMI_BOLD
)

After (3.x):

with(OpenWeb.manager.ui.customizations.elements.commentActions) {
    color = UIColor(lightColor = Color.parseColor("#FF5722"), darkColor = Color.parseColor("#FF5722"))
    fontWeight = OWFontWeight.SEMI_BOLD
}

setCustomUIDelegate → per-element callbacks

Replace the single CustomUIDelegate with a customizeView callback on the specific element. Declarative color/fontFamily/fontWeight are applied before the callback runs, and the callback receives an OWCustomizationContext (isDarkModeEnabled, postId).

Before (2.x):

OpenWeb.manager.ui.customizations.setCustomUIDelegate(object : CustomUIDelegate {
    override fun customizeView(
        viewType: CustomizableViewType,
        view: View,
        isDarkModeEnabled: Boolean,
        postId: OWPostId
    ) {
        when (viewType) {
            CustomizableViewType.NAVIGATION_TITLE_TEXT_VIEW ->
                (view as TextView).textSize = 18f
            CustomizableViewType.NAVIGATION_BACK_IMAGE_VIEW ->
                (view as ImageView).setColorFilter(Color.BLACK)
            else -> {}
        }
    }
})

After (3.x):

with(OpenWeb.manager.ui.customizations.elements) {
    navigationTitle.customizeView = { tv, ctx -> tv.textSize = 18f }
    navigationBackIcon.customizeView = { imageView, ctx ->
        imageView.setColorFilter(if (ctx.isDarkModeEnabled) Color.WHITE else Color.BLACK)
    }
}

CustomizableViewType value mapping:

CustomizableViewType (2.x)elements element (3.x)
NAVIGATION_TITLE_TEXT_VIEWnavigationTitle
LOGIN_PROMPT_TEXT_VIEWloginPrompt
COMMUNITY_QUESTION_TEXT_VIEWcommunityQuestion
COMMUNITY_GUIDELINES_TEXT_VIEWcommunityGuidelines
SAY_CONTROL_IN_PRE_CONVERSATION_TEXT_VIEWsayControlPreConversation
SAY_CONTROL_IN_CONVERSATION_TEXT_VIEWsayControlConversation
PRE_CONVERSATION_HEADER_TEXT_VIEWpreConversationHeaderText
PRE_CONVERSATION_HEADER_COUNTER_TEXT_VIEWpreConversationHeaderCounter
PRE_CONVERSATION_HEADER_USER_COUNT_TEXT_VIEWpreConversationHeaderUserCount
CONVERSATION_USERNAME_TEXT_VIEWcommenterName
CONVERSATION_COMMENT_COUNT_TEXT_VIEWconversationCommentCount
CONVERSATION_USER_COUNT_TEXT_VIEWconversationUserCount
CONVERSATION_SORT_TEXT_VIEWconversationSortText
READ_ONLY_TEXT_VIEWreadOnlyText
EMPTY_STATE_READ_ONLY_TEXT_VIEWemptyStateReadOnlyText
NAVIGATION_BACK_IMAGE_VIEWnavigationBackIcon
NAVIGATION_TOOLBAR_VIEWnavigationToolbar
SHOW_COMMENTS_BUTTONshowCommentsButton
COMMENT_CREATION_ACTION_BUTTONcommentCreationActionButton
COMMENT_CREATION_ACTION_IMAGE_BUTTONcommentCreationActionImageButton
CONVERSATION_FOOTER_VIEWconversationFooter
CONVERSATION_SORT_SPINNER_VIEWconversationSortSpinner

Fonts

Global font customization is unchanged — OpenWeb.manager.ui.customizations.fontFamily.styleResId = R.font.my_font still applies one font family across the SDK.

3.0.0 adds per-element fonts: set fontFamily and fontWeight (OWFontWeight) on individual elements via elements.<element>.

with(OpenWeb.manager.ui.customizations.elements) {
    commentBody.fontFamily = "my_custom_font"   // res/font/ name or a system family
    commentBody.fontWeight = OWFontWeight.BOLD
}

No migration is required for existing global-font code. See Custom Fonts for the full list of font-customizable elements and weight values.

Jetpack Compose

The new spotim-compose module provides PreConversation and Conversation composables for apps that use Jetpack Compose. This module is a new optional dependency — no migration is required if you are not using Compose. See the Jetpack Compose guide for setup instructions and full usage examples.