Skip to content

Commit

Permalink
Convert BrowserSwitchResult to Sealed Class (#88)
Browse files Browse the repository at this point in the history
* Rename BrowserSwitchResult
* Add BrowserSwitchResult sealed class
* Update result returned from parseResult
* Update demo activity
* Remove BrowserSwitchStatus
* Update CHANGELOG, README, and MIGRATION_GUIDE
* Update doc strings

Co-authored-by: sshropshire <[email protected]>
  • Loading branch information
sarahkoop and sshropshire authored Feb 7, 2024
1 parent 5d0c40c commit 0ab5c35
Show file tree
Hide file tree
Showing 15 changed files with 87 additions and 114 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
* Change `BrowserSwitchClient#parseResult` parameters
* Remove `deliverResult`, `getResult`, `captureResult`, `clearActiveRequests`, `getResultFromCache`, and `deliverResultFromCache` from `BrowserSwitchClient`
* Add `BrowserSwitchRequest` and `BrowserSwitchPendingRequest`
* Convert `BrowserSwitchResult` to sealed class and add `BrowserSwitchResultInfo`
* Remove `BrowserSwitchStatus`

## 2.6.1

Expand Down
18 changes: 10 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,14 +88,16 @@ override fun onResume() {

fun handleReturnToAppFromBrowser(intent: Intent) {
// fetch stored pending request
storedPendingRequest()?.let { startedRequest ->
val browserSwitchResult = browserSwitchClient.parseResult(startedRequest, intent)
if (browserSwitchResult != null) {
// handle successful browser switch result
// clear stored pending request
} else {
// user did not complete browser switch
// allow user to complete browser switch, or clear stored pending request
fetchPendingRequestFromPersistentStorage()?.let { startedRequest ->
when (val browserSwitchResult = browserSwitchClient.parseResult(startedRequest, intent)) {
is BrowserSwitchResult.Success -> {
// handle successful browser switch result
// clear stored pending request
}
is BrowserSwitchResult.NoResult -> {
// user did not complete browser switch
// allow user to complete browser switch, or clear stored pending request
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,18 +110,19 @@ private boolean isValidRequestCode(int requestCode) {
* {@link BrowserSwitchClient#start(ComponentActivity, BrowserSwitchOptions)}
* @param intent the intent to return to your application containing a deep link result from the
* browser flow
* @return a {@link BrowserSwitchResult} if the browser switch was successfully completed, or
* null if the user returned to the app without completing the browser switch
* @return a {@link BrowserSwitchResult.Success} if the browser switch was successfully
* completed, or {@link BrowserSwitchResult.NoResult} if no result can be found for the given
* {@link BrowserSwitchPendingRequest.Started}. A {@link BrowserSwitchResult.NoResult} will be
* returned if the user returns to the app without completing the browser switch flow.
*/
@Nullable
public BrowserSwitchResult parseResult(@NonNull BrowserSwitchPendingRequest.Started pendingRequest, @Nullable Intent intent) {
BrowserSwitchResult result = null;
if (intent != null && intent.getData() != null) {
Uri deepLinkUrl = intent.getData();
if (pendingRequest.getBrowserSwitchRequest().matchesDeepLinkUrlScheme(deepLinkUrl)) {
result = new BrowserSwitchResult(BrowserSwitchStatus.SUCCESS, pendingRequest.getBrowserSwitchRequest(), deepLinkUrl);
BrowserSwitchResultInfo resultInfo = new BrowserSwitchResultInfo(pendingRequest.getBrowserSwitchRequest(), deepLinkUrl);
return new BrowserSwitchResult.Success(resultInfo);
}
}
return result;
return BrowserSwitchResult.NoResult.INSTANCE;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@

import android.net.Uri;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.fragment.app.FragmentActivity;

import org.json.JSONObject;
Expand All @@ -26,7 +24,7 @@ public class BrowserSwitchOptions {
* Set browser switch metadata.
*
* @param metadata JSONObject containing metadata that will be persisted and returned in a
* {@link BrowserSwitchResult} when the app has re-entered the foreground
* {@link BrowserSwitchResultInfo} when the app has re-entered the foreground
* @return {@link BrowserSwitchOptions} reference to instance to allow setter invocations to be chained
*/
public BrowserSwitchOptions metadata(@Nullable JSONObject metadata) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.braintreepayments.api

/**
* The result of a browser switch obtained from [BrowserSwitchClient.parseResult]
*/
sealed class BrowserSwitchResult {

/**
* The browser switch was successfully completed. See [resultInfo] for details.
*/
class Success(val resultInfo: BrowserSwitchResultInfo) : BrowserSwitchResult()

/**
* No browser switch result was found. This is the expected result when a user cancels the
* browser switch flow without completing by closing the browser, or navigates back to the app
* without completing the browser switch flow.
*/
object NoResult : BrowserSwitchResult()
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,44 +8,28 @@
import org.json.JSONObject;

/**
* The result of the browser switch.
* Details of a successful {@link BrowserSwitchResult}
*/
public class BrowserSwitchResult {
public class BrowserSwitchResultInfo {

private static final String KEY_STATUS = "status";
private static final String KEY_DEEP_LINK_URL = "deepLinkUrl";
private static final String KEY_BROWSER_SWITCH_REQUEST = "browserSwitchRequest";

private final int status;
private final Uri deepLinkUrl;
private final BrowserSwitchRequest request;

static BrowserSwitchResult fromJson(String json) throws JSONException {
static BrowserSwitchResultInfo fromJson(String json) throws JSONException {
JSONObject jsonObject = new JSONObject(json);
int status = jsonObject.getInt(KEY_STATUS);
String deepLinkUrl = jsonObject.getString(KEY_DEEP_LINK_URL);
String browserSwitchRequest = jsonObject.getString(KEY_BROWSER_SWITCH_REQUEST);
return new BrowserSwitchResult(status, BrowserSwitchRequest.fromJson(browserSwitchRequest), Uri.parse(deepLinkUrl));
return new BrowserSwitchResultInfo(BrowserSwitchRequest.fromJson(browserSwitchRequest), Uri.parse(deepLinkUrl));
}

BrowserSwitchResult(@BrowserSwitchStatus int status, BrowserSwitchRequest request) {
this(status, request, null);
}

BrowserSwitchResult(@BrowserSwitchStatus int status, BrowserSwitchRequest request, Uri deepLinkUrl) {
this.status = status;
BrowserSwitchResultInfo(BrowserSwitchRequest request, Uri deepLinkUrl) {
this.request = request;
this.deepLinkUrl = deepLinkUrl;
}

/**
* @return The {@link BrowserSwitchStatus} of the browser switch
*/
@BrowserSwitchStatus
public int getStatus() {
return status;
}

/**
* @return A {@link JSONObject} containing metadata persisted through the browser switch
*/
Expand Down Expand Up @@ -79,7 +63,6 @@ public Uri getDeepLinkUrl() {

public String toJson() throws JSONException {
JSONObject result = new JSONObject();
result.put(KEY_STATUS, status);
result.put(KEY_DEEP_LINK_URL, deepLinkUrl.toString());
result.put(KEY_BROWSER_SWITCH_REQUEST, request.toJson());
return result.toString();
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
Expand Down Expand Up @@ -191,13 +190,12 @@ public void parseResult_whenActiveRequestMatchesDeepLinkResultURLScheme_returnsB
Intent intent = new Intent(Intent.ACTION_VIEW, deepLinkUrl);
BrowserSwitchResult browserSwitchResult = sut.parseResult(new BrowserSwitchPendingRequest.Started(request), intent);

assertNotNull(browserSwitchResult);
assertEquals(BrowserSwitchStatus.SUCCESS, browserSwitchResult.getStatus());
assertEquals(deepLinkUrl, browserSwitchResult.getDeepLinkUrl());
assertTrue(browserSwitchResult instanceof BrowserSwitchResult.Success);
assertEquals(deepLinkUrl, ((BrowserSwitchResult.Success) browserSwitchResult).getResultInfo().getDeepLinkUrl());
}

@Test
public void parseResult_whenDeepLinkResultURLSchemeDoesntMatch_returnsNull() {
public void parseResult_whenDeepLinkResultURLSchemeDoesntMatch_returnsNoResult() {
BrowserSwitchClient sut = new BrowserSwitchClient(browserSwitchInspector,
customTabsInternalClient);

Expand All @@ -209,11 +207,11 @@ public void parseResult_whenDeepLinkResultURLSchemeDoesntMatch_returnsNull() {
Intent intent = new Intent(Intent.ACTION_VIEW, deepLinkUrl);
BrowserSwitchResult browserSwitchResult = sut.parseResult(new BrowserSwitchPendingRequest.Started(request), intent);

assertNull(browserSwitchResult);
assertTrue(browserSwitchResult instanceof BrowserSwitchResult.NoResult);
}

@Test
public void parseResult_whenIntentIsNull_returnsNull() {
public void parseResult_whenIntentIsNull_returnsNoResult() {
BrowserSwitchClient sut = new BrowserSwitchClient(browserSwitchInspector,
customTabsInternalClient);

Expand All @@ -222,6 +220,6 @@ public void parseResult_whenIntentIsNull_returnsNull() {
new BrowserSwitchRequest(123, browserSwitchDestinationUrl, requestMetadata, "fake-url-scheme", false);

BrowserSwitchResult browserSwitchResult = sut.parseResult(new BrowserSwitchPendingRequest.Started(request), null);
assertNull(browserSwitchResult);
assertTrue(browserSwitchResult instanceof BrowserSwitchResult.NoResult);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import org.skyscreamer.jsonassert.JSONAssert;

@RunWith(RobolectricTestRunner.class)
public class BrowserSwitchResultUnitTest {
public class BrowserSwitchResultInfoUnitTest {

@Test
public void toJSON_serializesResult() throws JSONException {
Expand All @@ -24,11 +24,11 @@ public void toJSON_serializesResult() throws JSONException {
new BrowserSwitchRequest(123, requestUrl, requestMetadata, returnUrlScheme, true);

Uri deepLinkUrl = Uri.parse("example.return.url.scheme://success/ok");
BrowserSwitchResult sut = new BrowserSwitchResult(BrowserSwitchStatus.SUCCESS, request, deepLinkUrl);
BrowserSwitchResultInfo
sut = new BrowserSwitchResultInfo(request, deepLinkUrl);

BrowserSwitchResult sutSerialized = BrowserSwitchResult.fromJson(sut.toJson());
BrowserSwitchResultInfo sutSerialized = BrowserSwitchResultInfo.fromJson(sut.toJson());

assertEquals(BrowserSwitchStatus.SUCCESS, sutSerialized.getStatus());
assertEquals(deepLinkUrl, sutSerialized.getDeepLinkUrl());

assertEquals(123, sutSerialized.getRequestCode());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import com.braintreepayments.api.BrowserSwitchClient
import com.braintreepayments.api.BrowserSwitchOptions
import com.braintreepayments.api.BrowserSwitchPendingRequest
import com.braintreepayments.api.BrowserSwitchResult
import com.braintreepayments.api.BrowserSwitchResultInfo
import com.braintreepayments.api.demo.utils.PendingRequestStore
import com.braintreepayments.api.demo.viewmodel.BrowserSwitchViewModel
import org.json.JSONObject
Expand Down Expand Up @@ -48,11 +49,13 @@ class ComposeActivity : ComponentActivity() {
override fun onResume() {
super.onResume()
PendingRequestStore.get(this)?.let { startedRequest ->
val browserSwitchResult = browserSwitchClient.parseResult(startedRequest, intent)
browserSwitchResult?.let { result ->
viewModel.browserSwitchResult = result
} ?: run {
viewModel.browserSwitchError = Exception("User did not complete browser switch")
when (val browserSwitchResult =
browserSwitchClient.parseResult(startedRequest, intent)) {
is BrowserSwitchResult.Success -> viewModel.browserSwitchResult =
browserSwitchResult.resultInfo

is BrowserSwitchResult.NoResult -> viewModel.browserSwitchError =
Exception("User did not complete browser switch")
}
PendingRequestStore.clear(this)
}
Expand Down Expand Up @@ -106,7 +109,7 @@ fun BrowserSwitchButton(onClick: () -> Unit) {
}

@Composable
fun BrowserSwitchSuccess(result: BrowserSwitchResult) {
fun BrowserSwitchSuccess(result: BrowserSwitchResultInfo) {
result.deepLinkUrl?.let { returnUrl ->
val color = returnUrl.getQueryParameter("color")
val selectedColorString = "Selected color: $color"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ protected void onNewIntent(Intent intent) {
BrowserSwitchPendingRequest.Started pendingRequest = PendingRequestStore.Companion.get(this);
if (pendingRequest != null) {
BrowserSwitchResult result = browserSwitchClient.parseResult(pendingRequest, intent);
if (result != null) {
Objects.requireNonNull(getDemoFragment()).onBrowserSwitchResult(result);
if (result instanceof BrowserSwitchResult.Success) {
Objects.requireNonNull(getDemoFragment()).onBrowserSwitchResult(((BrowserSwitchResult.Success) result).getResultInfo());
}
PendingRequestStore.Companion.clear(this);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@

import com.braintreepayments.api.BrowserSwitchException;
import com.braintreepayments.api.BrowserSwitchOptions;
import com.braintreepayments.api.BrowserSwitchResult;
import com.braintreepayments.api.BrowserSwitchStatus;
import com.braintreepayments.api.BrowserSwitchResultInfo;

import org.json.JSONException;
import org.json.JSONObject;
Expand Down Expand Up @@ -100,25 +99,17 @@ private void clearTextViews() {
mMetadataTextView.setText("");
}

public void onBrowserSwitchResult(BrowserSwitchResult result) {
String resultText = null;
public void onBrowserSwitchResult(BrowserSwitchResultInfo result) {
String selectedColorText = "";

int statusCode = result.getStatus();
switch (statusCode) {
case BrowserSwitchStatus.SUCCESS:
resultText = "Browser Switch Successful";

Uri returnUrl = result.getDeepLinkUrl();
if (returnUrl != null) {
String color = returnUrl.getQueryParameter("color");
selectedColorText = String.format("Selected color: %s", color);
}
break;
case BrowserSwitchStatus.CANCELED:
resultText = "Browser Switch Cancelled by User";
break;
String resultText = "Browser Switch Successful";

Uri returnUrl = result.getDeepLinkUrl();
if (returnUrl != null) {
String color = returnUrl.getQueryParameter("color");
selectedColorText = String.format("Selected color: %s", color);
}

mBrowserSwitchStatusTextView.setText(resultText);
mSelectedColorTextView.setText(selectedColorText);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
package com.braintreepayments.api.demo.viewmodel

import androidx.lifecycle.ViewModel
import com.braintreepayments.api.BrowserSwitchClient
import com.braintreepayments.api.BrowserSwitchPendingRequest
import com.braintreepayments.api.BrowserSwitchResult
import com.braintreepayments.api.BrowserSwitchResultInfo
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
Expand All @@ -14,7 +12,7 @@ class BrowserSwitchViewModel : ViewModel() {
private val _uiState = MutableStateFlow(UiState())
val uiState: StateFlow<UiState> = _uiState.asStateFlow()

var browserSwitchResult : BrowserSwitchResult?
var browserSwitchResult : BrowserSwitchResultInfo?
get() = _uiState.value.browserSwitchResult
set(value) {
_uiState.update { it.copy(browserSwitchResult = value) }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package com.braintreepayments.api.demo.viewmodel

import com.braintreepayments.api.BrowserSwitchResult
import com.braintreepayments.api.BrowserSwitchResultInfo

data class UiState (
val browserSwitchResult: BrowserSwitchResult? = null,
val browserSwitchResult: BrowserSwitchResultInfo? = null,
val browserSwitchError: Exception? = null,
)
16 changes: 9 additions & 7 deletions v3_MIGRATION_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,15 @@ class MyActivity : ComponentActivity() {
fun handleReturnToAppFromBrowser(intent: Intent) {
// fetch stored pending request
fetchPendingRequestFromPersistentStorage()?.let { startedRequest ->
val browserSwitchResult = browserSwitchClient.parseResult(startedRequest, intent)
if (browserSwitchResult != null) {
// handle successful browser switch result
// clear stored pending request
} else {
// user did not complete browser switch
// allow user to complete browser switch, or clear stored pending request
when (val browserSwitchResult = browserSwitchClient.parseResult(startedRequest, intent)) {
is BrowserSwitchResult.Success -> {
// handle successful browser switch result
// clear stored pending request
}
is BrowserSwitchResult.NoResult -> {
// user did not complete browser switch
// allow user to complete browser switch, or clear stored pending request
}
}
}
}
Expand Down

0 comments on commit 0ab5c35

Please sign in to comment.