Clearing data on account removal - android

I want to clear the application's data when a user manually removes an account from the Accounts & sync section in the settings app.
I have my own implementation of AbstractAccountAuthenticator but there is no method to hook in the remove account process. Any hints?

I've been pondering on the same problem and here's the "solution" I decided upon. It's not what I'd call the "correct" solution but it's the best I believe you can manage with the current API.
In my implementation of the AbstractAccountAuthenticator class, I've overriden the getAccountRemovalAllowed function as follows:
#Override
public Bundle getAccountRemovalAllowed(
AccountAuthenticatorResponse response, Account account)
throws NetworkErrorException {
Bundle result = super.getAccountRemovalAllowed(response, account);
if (result != null && result.containsKey(AccountManager.KEY_BOOLEAN_RESULT)
&& !result.containsKey(AccountManager.KEY_INTENT)) {
final boolean removalAllowed = result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT);
if (removalAllowed) {
// Do my removal stuff here
}
}
return result;
}
There is a tiny chance that removal could fail AFTER you return from getAccountRemovalAllowed but it's negligible (IMHO).
As MisterSquonk suggested there is an Intent that you could listen for (ACCOUNTS_CHANGED_INTENT) but, unfortunately, it's broadcast when an account changes, and not just when an account is deleted.
I don't understand why this isn't part of the SDK but maybe we've both missed something obvious! For now, I'm sticking with this approach as I need to delete some database tables of my own on account deletion.
I hope this helps.

Related

How can I know when I need to dispose of a disposable in RxJava?

Trying to understand RxJava here. We have this code:
public void notifyNewOwnersSynced() {
OrgTreeType orgTreeType = getOrgTreeType();
new OrgTreeQuerier().queryOrgUserIds().byOrgTreeType(orgTreeType).executeAsync()
.map(opt -> opt.isPresent() ? opt.get() : new HashSet<String>(0))
.subscribe(
this::onNewOrgUserIds,
e -> Log.exception(new Exception("Unable to update selected id filter for type " + orgTreeType, e))
);
}
private void onNewOrgUserIds(#NonNull Set<String> allIds) {
synchronized (mLock) {
for (String id : allIds) {
if (!mPreviousAllIds.contains(id)) {
mSelectedIDs.add(id);
}
}
Set<String> idsNoLongerInHierarchy = new HashSet<>(); //Because we can't remove while we are iterating.
for (String selectedId : mSelectedIDs) {
//If there is a selected ID not in the new hierarchy...
if (!allIds.contains(selectedId)) {
//Plan to remove it.
idsNoLongerInHierarchy.add(selectedId);
}
}
mSelectedIDs.removeAll(idsNoLongerInHierarchy);
mPreviousAllIds = allIds;
}
mSaveListener.saveChangesAndPostFilterChangedEvent();
postSelectedIdsChangedEvent();
}
We have two lint warnings showing up on it: The result of subscribe is not used. and Result of single.subscribe() is ignored
This is in a class that is used by our UI(Fragments) to keep track of what users have been selected.
But on a larger scale we have a lot of spots in our code that use Rx like this to do something in the background (map something, network call, save data to the DB) and we don't every use the result.
Can I safely suppress these errors? or do I need to add handling for the Disposables?
How can I know when I need to dispose of a disposable?
Although not a comprehensive list of cases, I think I can point out some that I've faced during my career and might help you out.
The most common scenario I faced was when we make network calls that take too long and the app is put in the background. If not disposed, the result of the network will be forwarded to the subscriber. This is not really the issue. The problem is that usually the subscriber wants to change something UI related, which crashes the app. In this case, you dispose because you are no longer interested in receiving these events.
There are cases where the way the subscriber handles the result wouldn't be problematic, but the IDE has no way to know this and hence it warns you all the time.
I'm sure there are tons of other reasons why disposing should be handled - i.e., when observables acquire resources when subscribed and release them once unsubscribed from. So in general I guess it's good to handle the disposables. I only ever kept a disposable undisposed when I wanted to keep downloading files in the background and to be honest with you, I'm not even sure if this is a good practise.
Adding to this, if an observable or any of the other flavored observables (single, maybe, etc.) terminates, then it's disposed automatically.

Firebase Database stops syncing

I have been using Firebase Database in my Android app for almost a year now and it works pretty nice. Unfortunately the data stops being synced to the could after some time. It is just never synced/stored to the cloud. Only local. So when user reinstalls the app, it only contains the data which was stored in the cloud. So to the user it looks like the data was removed, but actually is was never stored. I checked and the data is not visible in the firebase-console. Because it happens after a reinstall I guess it has something to do with the syncing. Users report losing data of about 2-3 months.
I'm using the following singleton helper class. Note I use the setPersistenceEnabled(true) and keepSynced(true).
public class FirebaseHelper{
protected FirebaseHelper(Context c) {
this.c = c.getApplicationContext();
FirebaseDatabase.getInstance().setPersistenceEnabled(true);
mAuth = FirebaseAuth.getInstance();
this.userRef = FirebaseDatabase.getInstance().getReference().child(((BuildConfig.DEBUG ? "debug" : "release"))).child("users").child(getUID());
this.userRef.keepSynced(true);
this.path1 = userRef.child("path1");
this.path2 = userRef.child("path2");
this.path3 = userRef.child("path3");
this.path4 = userRef.child("path4");
}
public static FirebaseHelper getInstance(Context c) {
if (instance == null) {
instance = new FirebaseHelper(c);
}
return instance;
}
public String insertObject(MyObject obj) {
DatabaseReference newItem = this.path1.push();
String pushID = newItem.getKey();
obj.id = pushID;
newItem.setValue(obj.getObject());
return pushID;
}
public void updateData(...){}
...other methods
}
What could possibly be the cause of this?
There are only three reasons for this to happen to the best of my knowledge.
1) The method getUID()
Somehow the getUID() method is returning a null or invalid value which leads the data to be stored to somewhere else or it is not getting stored at all.
You are using simply getUid() instead of FirebaseAuth.getInstance().getCurrentUser().getUid(). So it must be a user defined method.
I think your getUid() does something like this.
String getUid() {
try{
return FirebaseAuth.getInstance().getCurrentUser().getUid() ;
} catch (NullPointerException e) {
return null;
}
}
FirebaseAuth.getInstance().getCurrentUser() will return null if cache is cleared. It will lead your data to be lost.
2) Redundant data
Since Marshmallow, data is backed up to the cloud including shared preference. If you are checking shared preference to decide if user is logged in, user will be gone taken inside after reinstalling the app, skipping the login page. But actually he is not logged in which means FirebaseAuth.getInstance().getCurrentUser() returns null and any attempt to access the database will fail (depends on your database rules).
Solution: use FirebaseAuth.getInstance().getCurrentUser()==null to check if user is enabled.
You can alternatively set backup to false. But I prefer first method.
3) An internal bug in Firebase SDK
Unfortunately there is nothing much we can do with it. Try narrowing it down and find a scenario by which the issue can be reproduced and report it to Firebase.
By the way, child(((BuildConfig.DEBUG ? "debug" : "release"))) is really smart. I am going to adopt it.
Well, if your database has been syncing and you have not made any changes to the code whatsoever, it means that this is a firebase error, particularly related to the mobile phone the user is using.
Most developers who use firebase find problems querying the database when certain carriers are used. I have researched into this issue but i have not yet resolved it yet. If you happen to be using mobile data, actions like authenticating a user may not work.
Solution
Use a different internet source to test your code. Try using wifi instead of mobile data while debugging or testing your app.
if I find any helpful work around, I will file it on a firebase project experiencing the problem on open source Lucem
Throwing this out as a guess as well:
At some point you distributed an app where BuildConfig.DEBUG = true, so users that install an updated version "lose" their data. Doesn't explain why other users haven't reported shorter losses though...
The solution would be a data migration, checking which has newer data and then copying the data if DEBUG is newer.

Properly handling when FirebaseInstanceId.getInstance().getToken() is null

What is a proper, solid way of having getToken() return with a token id?
I have seen a couple of attempts using
while-loop, calling getToken() until it's not null
Local BroadcastReceiver
Timer
The while loop seem really risky in terms of ANRs and having a loop running indefinitely.
The local BroadcastReceiver isn't readily applicable to my application, because even though there is a main activity in my app, there is nothing forcing the user to interact with it. Users can accomplish tasks in the app without going through that main activity.
The timer seem fragile. How long do you have to wait for? Seconds, minutes, hours?
The Firebase quickstart sample code does not provide an example to handle the situation when getToken() is null, and I don't see what a proper and solid implementation would be to ensure there's a token being returned, without any of the nasty side effects of the above mentioned implementations would bring.
Having a callback method to hook onto would've made this a non-issue, but since that isn't available, I just have to run this through StackOverflow to get an idea of how people resolve this issue.
Note: I have implemented onTokenRefresh() and that works for the specific situations stated in the docs, but I can't use that alone, because the app is only upgraded on a lot of devices, and this method isn't called during an app upgrade. I have the null issues on (Nexus) hardware devices, I am not using the emulator at all and don't intend to use it.
As it seems there is noone adding other approaches, than the ones I mentioned before, I'm now going to answer my own question.
The "solution" I have implemented to having a token being generated so that you can handle it more consistently, and store it for later reference, is this;
The following class follows the quickstart almost to the letter. But instead of sending the token away to a server, at the exact time it is accessible, I instead store it for later use:
public class FirebaseIdService extends FirebaseInstanceIdService {
#Override
public void onTokenRefresh() {
// Get updated InstanceID token.
String refreshedToken = FirebaseInstanceId.getInstance().getToken();
if(refreshedToken != null) {
Log.d("TOKENTAG", "Refreshed token: " + refreshedToken);
SharedPreferences prefs = getApplicationContext().getSharedPreferences("pref_id", 0);
prefs.edit().putString("share_pref_token", refreshedToken).apply();
}
}
}
When I want/need to send the token I check if there's already one available in the SharedPreferences and if not, I simply delete the Firebase instance, and call getToken(), to have another one generated. What this essentially does is to tell Firebase that "I want to invalidate the current instance", and then, by calling getToken() afterwards, have it generate a new one, essentially making onTokenRefresh() to be called, whenever a token has been generated and is available:
SharedPreferences prefs = getApplicationContext().getSharedPreferences("pref_id", 0);
if(prefs.getString("share_pref_token", "").isEmpty()) {
Log.d("TOKENTAG", "token not available, so let's force one to be generated");
try {
FirebaseInstanceId.getInstance().deleteInstanceId();
} catch (IOException e) {
e.printStackTrace();
}
FirebaseInstanceId.getInstance().getToken();
}
I am pretty sure this is a sub optimal solution, and I'm welcoming suggestions to make this suggestion better, but at this point this is basically the only way that seem to cover the bases of combining onTokenRefresh() and getToken() to more consistently get a token without any of the afformentioned drawbacks.

AccountManager which account was removed

I know that the AccountManager's addOnAccountsUpdatedListener() can be used to get notified about the changes of the list of accounts. If that kind of event happens, the framework will call the supplied OnAccountsUpdateListener's onAccountsUpdated() method. But the argument of the method only contains the list of accounts. How can i know which account was removed by the user? Thanks in advance!
Depending on what you’re trying to do, you might get away with this:
private Set<Account> mAccountCache; // init & populated when the listener is registered
#Override
public void onAccountsUpdated(Account[] accounts) {
// This code assumes we're only interested in removed items.
final Set<Account> currentAccounts = new HashSet<Account>(Arrays.asList(accounts));
final Set<Account> removedAccounts = new HashSet<Account>(mAccountCache);
removedAccounts.removeAll(currentAccounts); // populated with Accounts that were removed.
}

Android SyncAdapter Callback

I have implemented a SyncAdapter, AccountManager and private ContentProvider along the lines of the SimpleSyncAdapter sample project in the SDK. It is all working well.
Now I want to show a message to the user when new rows have been downloaded from the remote server that have a specific flag set. I need a callback from the SyncAdapter when a Sync has finished so I can do the query and display the message from an activity. I have seen a few questions on StackOverflow discussing this but none with a good answer.
How does one listen for progress from Android SyncAdapter? says that the SyncStatusObserver is useless. User mobibob suggests using a ResultReceiver to respond back to the UI from the sync thread.
How to know when sync is finished? suggests using an Intent in your SyncService.
How to signal sync-complete to the Android SyncManager? suggests using the SyncResult. The example code linked to by maxpower47 uses the SyncResult class to report exceptions but not to actually report if a sync was successfully completed.
I just don't know which is the best option and I have not seen any example projects where any of these solutions are used.
I know this is an old question, but I was asking the same thing myself.
What I found out as a good solution, specially because I'm dealing with local data as you are, is to use the following method from ContentResolver:
registerContentObserver(Uri uri, boolean notifyForDescendents, ContentObserver observer)
This register an observer class that get callback when data identified by a given content URI changes. But that can only happens if your ContentProvider send the notification. So for example, if you want to get notified on the ContentObserver above for all updates done on your database through a ContentProvider, your ContentProvider should implement update similar to this:
#Override
public int update(Uri uri, ContentValues contentValues, String s, String[] strings) {
// code goes here
this.getContext().getContentResolver().notifyChange(uri, null);
return 0;
}
Using notifyForDescendents when you do a registerContentObserver can be very useful.
This is an old question but I did some research in the past days and there are not many exemples on syncAdapter handling network requests and notifying the UI.
First you should use Loaders with contentProvider to make your life easier. You don't need to register for content resolver anymore the Loader does it for you. So it means your UI gets notified for anything that goes into your content provider.
What if nothing changed ? everything was up to date or you had a network error.
You can listen to the status of your syncAdapter as the Google I/O
app does, search for mSyncStatusObserver in the BaseActivity
I had a look at the default android email app and they use a Singleton with callBacks.
You can BroadcastIntents or use an eventBus (square Otto for exemple) to notify your UI of any behaviour.
I like the last one better because it gives you more granularity on the events that happen in the syncAdapter.
We ran into a similar situation and wrote a static Listener interface to the SyncAdapter. The listener is the activity and performs necessary actions when the data is available (update UI). This also works when the sync-adapter is called by the system during autosync where this listener would be null and the sync process would mind its own business.
class SyncAdapter extends AbstractThreadedSyncAdapter {
protected static Listener uiListener = null;
public interface Listener {
public void onSync();
}
public static void setListener(Listener l) {
uiListener = l;
}
public static void clearListener() {
uiListener = null;
}
protected void broadcastSync() {
if (uiListener != null)
uiListener.onSync();
}
public void onPerformSync(Account account, Bundle extras, String authority,
ContentProviderClient provider, SyncResult syncResult) {
// call broadcastSync();
}
Then in the Activity, implement SyncAdapter.Listener interface.

Categories

Resources