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.
}
Related
So I've implemented IAP in Android using Unity but one thing remains is how to validate a user across multiple devices.
For instance, user purchases a consumable on one phone and then logs in on another phone...
How can I validate the user has made a purchase on the other phone? The purchase is tied to the google account and not the phone. So is there a way to get info about the google account that is logged in currently? Then I could save the receipt behind the scenes and verify when the user logs in on the other phone against a server. But without some unique identifier across phones I would have to force the user to create an account which I do not want to do.
I am currently developing an iOS/Android game. The approach I took to this issue was utilizing the Cloud Save feature offered by Google/Apple. When saving/loading data, there is the chance that there is a conflict with the local vs. cloud data. When there is a conflict, it is here where you will be able to merge data between the local and remote data.
There is a decision at least for Google to choose between the callback resolution of automatically resolving the conflict using one of their default conflict resolutions (longest playtime, newest data, etc.) or manually resolving the conflict. You will want to manually resolve the conflict so you can merge the data together from both saves.
What is important is how you now save this data to the cloud and how you serialize purchases. I will show how I am handling this, but you can approach it in a different way.
[System.Serializable]
public class IndividualPurchaseDataSave
{
public IndividualPurchaseDataSave(string id, bool used)
{
purchaseID = id;
isApplied = used;
}
public string purchaseID = "";
public bool isApplied = false;
}
/// <summary>
/// All purchase data
/// </summary>
[System.Serializable]
public class PurchaseDataSave
{
public List<IndividualPurchaseDataSave> PurchaseData = new List<IndividualPurchaseDataSave>();
}
I have two structures, the first being an individual purchase where the second structure holds a list of all purchases that can be serialized in JSON to push to the cloud.
When the user buys a new in-app purchase, after receiving the Google callback confirmation, I call this function with the purchaseID.
/// <summary>
/// Purchases an item and adds it to the dictionary for later usage
/// </summary>
/// <param name="purchaseID"></param>
public void PurchaseItem(string purchaseID)
{
// adding a delimeter of | to split it later in case we need to handle merging data
PlayerInAppPurchaseHistory.Add(purchaseID + "|" + System.DateTimeOffset.Now.ToUnixTimeMilliseconds(), true);
}
The dictionary structure looks as follows:
private Dictionary<string, bool> PlayerInAppPurchaseHistory = new Dictionary<string, bool>();
I am adding the purchaseID along with the time purchased in milliseconds as that should be unique enough to never occur again. Wrapping it in a delimiter allows me to still split the string if I need the purchaseID for other processing.
When merging the data together, you receive a byte[] from Google and need to convert this data back to something of use. Once you are able to receive the remote data purchase data, you can merge them using a HashSet.
// merge the remote INTO the local
HashSet<string> purchaseData = new HashSet<string>();
// add our existing purchases to a hashset
foreach (IndividualPurchaseDataSave data in localPurchaseData.PurchaseData)
{
purchaseData.Add(data.purchaseID);
}
// now iterate over our remote purchases and if anything is missing, add it to our temp data
foreach (IndividualPurchaseDataSave data in remotePurchaseDate.PurchaseData)
{
// purchase does not exist, so add it and set it as not applied (false)
if (!purchaseData.Contains(data.purchaseID))
{
localPurchaseData.PurchaseData.Add(new IndividualPurchaseDataSave(data.purchaseID, false));
}
}
After merging the data, you can cache which values are added, but I am reloading the scene as my merge conflict resolution is a bit more complex, so I am adding the new purchases in the Load
foreach(IndividualPurchaseDataSave purchaseData in data.PurchaseData)
{
// if the data is not processed, then process it now - split it over the time
if (!purchaseData.isApplied)
ProcessPurchaseItem(purchaseData.purchaseID.Split('|')[0], false);
// add it to our dictionary
PlayerInAppPurchaseHistory.Add(purchaseData.purchaseID, true);
}
Effectively storing the data in some identifiable way that can be merged at a later time is how you will want to approach this issue. Using cloud saving has no additional cost to me, so that is why I decided to utilize the cloud for this issue. This code is not functional as is, it should be used as a guide.
I recently ran into discussion about usage of Subject, like this one here: https://github.com/JakeWharton/RxRelay/issues/7
I see a lot of people saying that Subject should be avoided and some people even say any usage of Subject is inherently a bad practice. While I agree on the theoretical level that Subject can be and should be avoided, I can hardly get rid of subjects in real practices. It seems impractical, or even impossible to do so.
Imagine a simple theoretical weather app that has just two things:
a view that displays current weather information
a refresh button which re-fetch the weather information from the server.
(Let's assume for simplicity that the app does not show the data at initial launch, but waits for the users to press refresh button at least once.)
Then you can think of a view model design like this:
ViewModel
interface IWeatherViewModel {
// Provides weather data
Flowable<WeatherData> getWeatherDataToDisplay();
// Lets view to refresh
void refresh();
}
If I use Subject then IWeatherViewModel can be implemented like this:
class WeatherViewModel implements IWeatherViewModel {
private final BehaviorProcessor<WeatherData> weatherData = BehaviorProcessor.create();
private final PublishProcessor<Boolean> eventRefresh = PublishProcessor.create();
WeatherViewModel() {
eventRefresh
.flatMapSingle(x -> getWeatherData())
.subscribe(weatherData);
}
// Provides weather data
public Flowable<WeatherData> getWeatherDataToDisplay() {
weatherData.hide();
}
// Lets view to refresh
public void refresh() {
eventRefresh.onNext(true);
}
private Single<WeatherData> getWeatherData() {
... // omitted for simplicity
}
}
The idea is to have a PublishProcessor that emits refresh event whenever refresh() is called which is then propagated to a BehaviorSubject. All subscribers that observe getWeatherDataToDisplay() will be notified once getWeatherData() is successful.
However I find it difficult to implement the same thing without Subject.
The app needs to propagate refresh() call to stream. I might be able to replace PublishProcessor using Flowable.create() but it doesn't look clean at all, the best I could do is:
private FlowableEmitter emitter;
private final Flowable<Boolean> eventRefresh = Flowable.create(emitter -> {
this.emitter = emitter;
}, BackpressureStrategy.BUFFER);
public void refresh() {
emitter.onNext(true);
}
Now suddenly I have to have a new instance variable that I cannot make final..
Also I am not able to find any operator that can effectively replace BehaviorProcessor, nor any hot observable that emits the latest item immediately on subscription. This behavior is necessary because the view should be able to detach and re-attach seamlessly, just like LiveData.
If you see any improvement that can be made, or have a different approach to the problem, please share your thougts.
I'm trying to understand notification types in Realm from the Notifications section in the official docs, and when I'm using RealmObject addChangeListener in multiple managed object all of them are called when only one object is changing.
This is my code
Person first = realm.where(Person.class).equalTo("id", 0).findFirst();
first.addChangeListener(new RealmChangeListener<Person>() {
#Override
public void onChange(Person person) {
Log.e(LOG_TAG, "First element is changing: " + person);
}
});
Person second = realm.where(Person.class).equalTo("id", 1).findFirst();
second.addChangeListener(new RealmChangeListener<Person>() {
#Override
public void onChange(Person person) {
Log.e(LOG_TAG, "Second person is changing: " + person);
}
});
When I trigger an update in any of these Person objects (for example in first) both of the listeners are being called.
This what official docs say:
Listeners can also be attached to RealmObject instances as well as RealmResults instances. This allows you to react to changes to your objects and query results.
And
Lastly, typed-based change listeners will get notified when their referenced types change.
From what I understand the seen behaviour agrees with the second definition but I need to use the first behaviour, that's, I want to be notified when the object corresponding to that listener is changed.
So, if first Person is updated, only its corresponding listener get notified, not all Person listeners.
Right now it is happening because our change detection is not granular enough. It will trigger change listeners for all objects of the same type, not just those that changed.
Getting the changelisteners to only notify if the exact object has changed is being tracked here https://github.com/realm/realm-java/issues/989.
Use findFirstAsync() which returns a realmModel being empty and invalid. This is how you are able to get updates with its addChangeListener()
Specific change listener is now supported.
official release!
Demo
I am new to Firebase and need some help with a query to retrieve data from a table. I am currently able to access and retrieve the data that I need from firebase, however, the timing is the problem I am having an issue with.
From everything I've seen, the firebase database requires me to add event listeners to the Query or DatabaseReference objects. I am trying to download the contents of a node called "questions" before a method to display the question contents is called, however, I cannot control the timing of the firing of the event which downloads the data, and as a result my display method is always called before the firebase event fires.
How can I execute a query when I want, and be sure it will be completed before a certain section of my code executes? I am used to traditional RDBs where you execute a query and get its results and then move forward with your logic. The need to use an event handler with firebase is what I am having a hard time with. I have even tried moving the definition of the firebase reference object and the event handler into onCreate() and moved the code that calls my display method into onStart() without any success - same problem. The data I am trying to get does not change so I only need to download it once at the beginning to have available for the display method.
Here is an image of my "questions" node which is a child of the root.
image of the child "questions" node on my firebase DB
Here is my code:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Get Firebase DB reference
firebase = FirebaseDatabase.getInstance();
fdbRef = firebase.getReference("questions");
// [START Question_event_listener]
fdbRef.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
// Get Questions object and use the values to update the UI
objQuestions = dataSnapshot.getValue();
Log.w("Firebase:", "In Firebase ValueEventListener");
}
#Override
public void onCancelled(DatabaseError databaseError) {
// Getting Questions failed, log a message
Log.w("Firebase Error:", "onCancelled:", databaseError.toException());
Toast.makeText(ReviewActivity.this, "Failed to load question!", Toast.LENGTH_SHORT).show();
}
});
//. . . remaining onCreate logic removed for simplicity
} //end of onCreate
#Override
public void onStart() {
super.onStart();
// I moved this logic from onCreate to onStart but did not help...
// Firebase retrieve must execute before I call any of these
if (list_type == MainActivity.LIST_UNREVIEWED_DOCS)
displayNewReviewForm();
else if (list_type == MainActivity.LIST_REVIEWS)
displayCompletedReview();
else // (list_type == MainActivity.LIST_DRAFTS)
displayDraftReview();
}
Other alternatives if I can't get this resolved may be to move this retrieve logic to the prior Activity in my sequence and pass the retrieved data as an extra to this activity - but that seems really silly to have to do such a thing. I would think I should be able to get data from a DB when I need it... not when it feels like giving it to me.
I appreciate any help getting me past this issue.
Your code is downloading the snapshot data containing all the data at the first go only, and with Firebase, you cannot download data timely, you can only do it through different references.
What I would suggest you to do is, to have a DatabaseReference of q01, q02 respectively and then call data as in when required.
If your Keys "q01", "q02" are static, which they are looking at the scenario. I would suggest you to have their DatabaseReferences:
question_one = firebase.getReference("q01");
question_two = firebase.getReference("q02");
question_three = firebase.getReference("q03");
//Once you have the reference, you can call their ValueListeners respectively
question_one.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
// Get Questions object and use the values to update the UI
objQuestions = dataSnapshot.getValue();
Log.w("Firebase:", "In Firebase ValueEventListener");
}
#Override
public void onCancelled(DatabaseError databaseError) {
// Getting Questions failed, log a message
Log.w("Firebase Error:", "onCancelled:", databaseError.toException());
Toast.makeText(ReviewActivity.this, "Failed to load question!", Toast.LENGTH_SHORT).show();
}
});
After looking at this a bit more, I came up with 2 possible solutions to the problem I had.
The first one I sort of mentioned already in my original question post, however it's not ideal in my opinion. It basically involves relocating the firebase retrieve logic to the prior Android Activity and passing the retrieved data to the Activity I need it in as an Extra. In my case the data is a HashMap so I would need to use the serialize versions of the methods to pass the serialized content to the desired Activity.
The best solution, is much simpler. I basically relocated the logic that I had in the onStart() function (which is calling my custom display methods) and moved it inside of the Firebase Event Listener's onDataChange() method, right after the call to dataSnapshot.getValue(). This ensures that I get the data before I call my display methods. This seems to be working well now.
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.