Skip to content

Commit

Permalink
Add reactive way to handle activity results
Browse files Browse the repository at this point in the history
If no user is logged in or its auth tokens are outdated we sometimes
have to wait for the main activity to be around until can switch to the
AuthActivity. This is now properly implemented to work in a reactive
way. This solves an issue when migrating from the 1.5 version where the
app was only usable after a restart because we were not able to react to
the new auth tokens to become available.
  • Loading branch information
saemy committed Jun 2, 2018
1 parent 7a46f89 commit c2d7f32
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 38 deletions.
115 changes: 77 additions & 38 deletions app/src/main/java/fi/bitrite/android/ws/auth/AccountManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
import fi.bitrite.android.ws.di.AppScope;
import fi.bitrite.android.ws.ui.MainActivity;
import fi.bitrite.android.ws.util.MaybeNull;
import io.reactivex.Maybe;
import io.reactivex.MaybeObserver;
import io.reactivex.Observable;
import io.reactivex.Single;
import io.reactivex.functions.Function;
Expand All @@ -45,7 +47,7 @@ public class AccountManager {
private final Observable<Integer> mCurrentUserId;

private Activity mMainActivity = null;
private Intent mCreateOrAuthAccountIntent = null;
private EventuallyCreateOrAuth mEventuallyCreateOrAuth = null;

@Inject
AccountManager(WarmshowersWebservice generalWebservice,
Expand Down Expand Up @@ -86,7 +88,7 @@ public class AccountManager {
});
} else {
// We have an account and therefore no longer need to create a new one.
mCreateOrAuthAccountIntent = null;
dismissEventuallyCreateOrAuth();
}
});

Expand Down Expand Up @@ -122,11 +124,13 @@ public Observable<Integer> getCurrentUserId() {
*/
public void setMainActivity(MainActivity mainActivity) {
mMainActivity = mainActivity;
if (mCreateOrAuthAccountIntent != null) {
if (mEventuallyCreateOrAuth != null) {
// The main activity was not around when we wanted to show the account creation screen.
// Do it now.
mMainActivity.startActivity(mCreateOrAuthAccountIntent);
mCreateOrAuthAccountIntent = null;
final Intent intent = mEventuallyCreateOrAuth.intent;
final MaybeObserver<? super Bundle> observer = mEventuallyCreateOrAuth.observer;
mEventuallyCreateOrAuth = null;
startActivityForResult(intent, observer);
}
}

Expand All @@ -144,22 +148,17 @@ public Single<Account> createNewAccount() {
try {
Bundle result = accountManagerFuture.getResult();

boolean containsIntent = handleIntentInBundle(result);
if (containsIntent) {
// FIXME(saemy): This callback does not get called a second time when the
// login completed. So we need to find a way to call the below onSuccess()
// method...
return;
}

String name =
result.getString(android.accounts.AccountManager.KEY_ACCOUNT_NAME);
String type =
result.getString(android.accounts.AccountManager.KEY_ACCOUNT_TYPE);
handleIntentInBundle(result)
.subscribe(result2 -> {
final String name = result2.getString(
android.accounts.AccountManager.KEY_ACCOUNT_NAME);
final String type = result2.getString(
android.accounts.AccountManager.KEY_ACCOUNT_TYPE);

// TODO(saemy): Mark this account as the active one.
// TODO(saemy): Mark this account as the active one.

emitter.onSuccess(new Account(name, type));
emitter.onSuccess(new Account(name, type));
}, emitter::onError);
} catch (Exception e) {
emitter.onError(e);
}
Expand Down Expand Up @@ -203,19 +202,14 @@ public Single<AuthToken> getAuthToken(@NonNull Account account) {
try {
Bundle result = tokenFuture.getResult();

boolean containsIntent = handleIntentInBundle(result);
if (containsIntent) {
// FIXME(saemy): This callback does not get called a second time when the
// login completed. So we need to find a way to call the below onSuccess()
// method...
return;
}
handleIntentInBundle(result)
.subscribe(result2 -> {
String authTokenStr = result2.getString(
android.accounts.AccountManager.KEY_AUTHTOKEN);
AuthToken authToken = AuthToken.fromString(authTokenStr);

String authTokenStr =
result.getString(android.accounts.AccountManager.KEY_AUTHTOKEN);
AuthToken authToken = AuthToken.fromString(authTokenStr);

emitter.onSuccess(authToken);
emitter.onSuccess(authToken);
}, emitter::onError);
} catch (Exception e) {
emitter.onError(e);
}
Expand All @@ -235,18 +229,46 @@ public Single<AuthToken> getAuthToken(@NonNull Account account) {
});
}

private boolean handleIntentInBundle(Bundle result) {
/**
* Calls the intent that is stored in the given bundle. If no main activity is started yet, the
* intent is saved for later usage. If no intent is saved in the bundle nothing is done.
*
* @return
* The single that is triggered as soon as the final bundle is available. That is the
* one given in case no intent is in it or the one that is eventually returned from the
* started activity.
*/
private Maybe<Bundle> handleIntentInBundle(Bundle result) {
if (!result.containsKey(android.accounts.AccountManager.KEY_INTENT)) {
return false;
return Maybe.just(result);
}

final Intent intent = result.getParcelable(android.accounts.AccountManager.KEY_INTENT);
if (mMainActivity != null) {
mMainActivity.startActivity(intent);
return new Maybe<Bundle>() {
@Override
protected void subscribeActual(MaybeObserver<? super Bundle> observer) {
Intent intent = result.getParcelable(android.accounts.AccountManager.KEY_INTENT);
startActivityForResult(intent, observer);
}
};
}

private void startActivityForResult(Intent intent, MaybeObserver<? super Bundle> observer){
MainActivity mainActivity = (MainActivity) mMainActivity;
if (mainActivity != null) {
mainActivity.startActivityForResultRx(intent)
.subscribe(intent2 -> observer.onSuccess(intent2.getExtras()),
observer::onError);
} else {
mCreateOrAuthAccountIntent = intent;
dismissEventuallyCreateOrAuth();
mEventuallyCreateOrAuth = new EventuallyCreateOrAuth(intent, observer);
}
}

private void dismissEventuallyCreateOrAuth() {
if (mEventuallyCreateOrAuth != null) {
mEventuallyCreateOrAuth.observer.onComplete();
mEventuallyCreateOrAuth = null;
}
return true;
}

/**
Expand Down Expand Up @@ -306,6 +328,13 @@ public Observable<LoginResult> login(String username, String password) {

public int getUserId(@NonNull Account account) {
return executeWithReadLock(v -> {
// Migration from version <2.0.0.
String oldUserIdStr = mAndroidAccountManager.getUserData(account,"userid");
if (oldUserIdStr != null) {
mAndroidAccountManager.setUserData(account, KEY_USER_ID, oldUserIdStr);
mAndroidAccountManager.setUserData(account, "userid", null);
}

String userIdStr = mAndroidAccountManager.getUserData(account, KEY_USER_ID);
return userIdStr != null
? Integer.parseInt(userIdStr)
Expand Down Expand Up @@ -394,4 +423,14 @@ public AuthData authData() {
return mAuthData;
}
}

class EventuallyCreateOrAuth {
final Intent intent;
final MaybeObserver<? super Bundle> observer;

EventuallyCreateOrAuth(Intent intent, MaybeObserver<? super Bundle> observer) {
this.intent = intent;
this.observer = observer;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,11 @@ public void login() {

setAccountAuthenticatorResult(response);

// The following we add for usage in MainActivity::onActivityResult().
Intent intent = new Intent();
intent.putExtras(response);
setResult(0, intent);

finish();
} else {
mProgressDisposable.dispose();
Expand Down
27 changes: 27 additions & 0 deletions app/src/main/java/fi/bitrite/android/ws/ui/MainActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.text.TextUtils;
import android.util.SparseArray;
import android.view.Gravity;
import android.widget.ImageView;
import android.widget.ListView;
Expand Down Expand Up @@ -47,6 +48,8 @@
import fi.bitrite.android.ws.ui.util.NavigationController;
import fi.bitrite.android.ws.util.LoggedInUserHelper;
import io.reactivex.Observable;
import io.reactivex.Single;
import io.reactivex.SingleObserver;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
Expand Down Expand Up @@ -310,6 +313,30 @@ public NavigationController getNavigationController() {
return mNavigationController;
}

private int mNextRequestCode = 0;
private final SparseArray<SingleObserver<? super Intent>> mActivityResultReactors =
new SparseArray<>();
public Single<Intent> startActivityForResultRx(Intent intent) {
return new Single<Intent>() {
@Override
protected void subscribeActual(SingleObserver<? super Intent> observer) {
int requestCode = mNextRequestCode++;
mActivityResultReactors.append(requestCode, observer);
MainActivity.super.startActivityForResult(intent, requestCode);
}
};
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
SingleObserver<? super Intent> observer = mActivityResultReactors.get(requestCode);
if (observer != null) {
mActivityResultReactors.remove(requestCode);
observer.onSuccess(data);
} else {
super.onActivityResult(requestCode, resultCode, data);
}
}

/**
* This class can inject itself into an accountComponent. This is the bridge to get access to
* the account scope from the appScoped {@link MainActivity}.
Expand Down

0 comments on commit c2d7f32

Please sign in to comment.