Multiple Nested Callbacks from Async Class - java

Fairly new to Android/Java development and using the Open Source Parseplatform as my backend server. I've created a class to manage a parse object and this object update's its data from an async call to the parse server as per this code.
public class DeviceObject {
private String objectID, deviceName, status;
private ParseGeoPoint location;
int batLevel;
public DeviceObject(){
objectID = null;
deviceName = null;
location = null;
batLevel = 0;
status = null;
}
public void getDeviceLatestData() {
if (objectID != null) {
ParseQuery<ParseObject> query = ParseQuery.getQuery("DeviceData");
query.whereEqualTo("DeviceObjectID", objectID);
query.orderByDescending("createdAt");
query.setLimit(1);
query.findInBackground(new FindCallback<ParseObject>() {
public void done(List<ParseObject> ParseDeviceList, ParseException e) {
if (e == null) {
if (ParseDeviceList.size() == 0) {
Log.d("debg", "Device not found");
} else {
for (ParseObject ParseDevice : ParseDeviceList) {
status = ParseDevice.getString("Status");
batLevel = ParseDevice.getInt("BatteryLevel");
location = ParseDevice.getParseGeoPoint("Location");
Log.d("debg", "Retrieving: " + deviceName);
Log.d("debg", "Status: " + status + " Battery: " + Integer.toString(batLevel));
}
//callback listener to add marker to map
}
} else {
Log.d("debg", "Error: " + e.getMessage());
}
}
});
}
}
So I create my class object in my Main Activity with the following:
DeviceObject userDevice = new DeviceObject();
userDevice.getDeviceLatestData();
What I can't get my head around is how in my MainActivity I can get notified/callback to continue displaying the information which the userDevice class just got off the parse Server.
I've tried creating an interface and adding a listener as what i've seen suggested however I could not add the listener inside the parse's done function.
The definition of my main activity is, note I need the OnMapReadyCallback as i'm using Google Maps
public class MapMainActivity extends AppCompatActivity implements OnMapReadyCallback {
So in summary i'd like to add something to the main activity so that I can process the data when it has been added to the class from the async call.

For something like this, I recommend using an event bus. Here is a link to a popular one I've had success with in the past.
Basically, you will have another class involved, which will be your bus. Your activity will register for a specific event (which you will create, subclassing as appropriate). Your async call will tell the event bus to fire off that event, and the bus will then tell all subscribers, including your main activity, that the event fired off. That is when you'd call getDeviceLatestData. Below are simple code snippets you may use, but read the documentation on that bus to fully understand it.
Your event:
public static class DataReady Event { /* optional properties */ }
Your DeviceObject:
public class DeviceObject {
private String objectID, deviceName, status;
private ParseGeoPoint location;
int batLevel;
public DeviceObject(){
objectID = null;
deviceName = null;
location = null;
batLevel = 0;
status = null;
}
public void getDeviceLatestData() {
if (objectID != null) {
ParseQuery<ParseObject> query = ParseQuery.getQuery("DeviceData");
query.whereEqualTo("DeviceObjectID", objectID);
query.orderByDescending("createdAt");
query.setLimit(1);
query.findInBackground(new FindCallback<ParseObject>() {
public void done(List<ParseObject> ParseDeviceList, ParseException e) {
if (e == null) {
if (ParseDeviceList.size() == 0) {
Log.d("debg", "Device not found");
} else {
for (ParseObject ParseDevice : ParseDeviceList) {
status = ParseDevice.getString("Status");
batLevel = ParseDevice.getInt("BatteryLevel");
location = ParseDevice.getParseGeoPoint("Location");
Log.d("debg", "Retrieving: " + deviceName);
Log.d("debg", "Status: " + status + " Battery: " + Integer.toString(batLevel));
}
//callback listener to add marker to map
EventBus.getDefault().post(new DataReadyEvent());
}
} else {
Log.d("debg", "Error: " + e.getMessage());
}
}
});
}
}
Your MainActivity:
public class MainActivity {
#Override
public void onStart() {
super.onStart();
EventBus.getDefault().register(this);
}
#Override
public void onStop() {
EventBus.getDefault().unregister(this);
super.onStop();
}
#Subscribe(threadMode = ThreadMode.MAIN) // Seems like you're updating UI, so use the main thread
public void onDataReady(DataReadyEvent event) {
/* Do whatever it is you need to do - remember you can add properties to your event and pull them off here if you need to*/
};
}

Related

Stay logged in with google unity firebase

I am making an app. I added google sign in and its working fine, but
everytime i close it and start the app, it get signed out. Please tell
me how to stay logged in with google.
using System; using
System.Collections; using System.Collections.Generic; using
System.IO; using System.Linq; using System.Threading.Tasks; using
Firebase; using Firebase.Auth; using Google; using UnityEngine;
using UnityEngine.UI; using UnityEngine.Networking;
public class GoogleSignInDemo : MonoBehaviour {
public Text infoText;
public Text Name;
public Text Email;
public RawImage ProfileImage;
public RawImage ProfileImage1;
public Button disable;
public string webClientId = "<your client id here>";
private FirebaseAuth auth;
private GoogleSignInConfiguration configuration;
private void Awake()
{
configuration = new GoogleSignInConfiguration { WebClientId = webClientId, RequestEmail = true, RequestIdToken = true };
CheckFirebaseDependencies();
}
private void CheckFirebaseDependencies()
{
FirebaseApp.CheckAndFixDependenciesAsync().ContinueWith(task =>
{
if (task.IsCompleted)
{
if (task.Result == DependencyStatus.Available)
auth = FirebaseAuth.DefaultInstance;
else
AddToInformation("Could not resolve all Firebase dependencies: " + task.Result.ToString());
}
else
{
AddToInformation("Dependency check was not completed. Error : " + task.Exception.Message);
}
});
}
public void SignInWithGoogle() { OnSignIn(); }
public void SignOutFromGoogle() { OnSignOut(); }
private void OnSignIn()
{
GoogleSignIn.Configuration = configuration;
GoogleSignIn.Configuration.UseGameSignIn = false;
GoogleSignIn.Configuration.RequestIdToken = true;
AddToInformation("Calling SignIn");
GoogleSignIn.DefaultInstance.SignIn().ContinueWith(OnAuthenticationFinished);
}
private void OnSignOut()
{
AddToInformation("Calling SignOut");
GoogleSignIn.DefaultInstance.SignOut();
}
public void OnDisconnect()
{
AddToInformation("Calling Disconnect");
GoogleSignIn.DefaultInstance.Disconnect();
}
internal void OnAuthenticationFinished(Task<GoogleSignInUser> task)
{
if (task.IsFaulted)
{
using (IEnumerator<Exception> enumerator = task.Exception.InnerExceptions.GetEnumerator())
{
if (enumerator.MoveNext())
{
GoogleSignIn.SignInException error = (GoogleSignIn.SignInException)enumerator.Current;
AddToInformation("Got Error: " + error.Status + " " + error.Message);
}
else
{
AddToInformation("Got Unexpected Exception?!?" + task.Exception);
}
}
}
else if (task.IsCanceled)
{
AddToInformation("Canceled");
}
else
{
AddToInformation("Welcome: " + task.Result.DisplayName + "!");
AddToInformation("Email = " + task.Result.Email);
AddToInformation("Google ID Token = " + task.Result.IdToken);
AddToInformation("ImageUrl = " + task.Result.ImageUrl);
AddToInformation("Email = " + task.Result.Email);
SignInWithGoogleOnFirebase(task.Result.IdToken);
Name.text = task.Result.DisplayName;
Email.text = task.Result.Email;
disable.enabled = false;
PlayerPrefs.SetString("Name", task.Result.DisplayName);
PlayerPrefs.SetString("Email", task.Result.Email);
String stringUri;
stringUri = task.Result.ImageUrl.ToString();
PlayerPrefs.SetString("ImageURL", stringUri);
StartCoroutine(DownloadImage(stringUri));
IEnumerator DownloadImage(string MediaUrl)
{
UnityWebRequest request = UnityWebRequestTexture.GetTexture(MediaUrl);
yield return request.SendWebRequest();
if (request.isNetworkError || request.isHttpError)
Debug.Log(request.error);
else
ProfileImage.texture = ((DownloadHandlerTexture)request.downloadHandler).texture;
ProfileImage1.texture = ((DownloadHandlerTexture)request.downloadHandler).texture;
}
}
}
private void SignInWithGoogleOnFirebase(string idToken)
{
Credential credential = GoogleAuthProvider.GetCredential(idToken, null);
auth.SignInWithCredentialAsync(credential).ContinueWith(task =>
{
AggregateException ex = task.Exception;
if (ex != null)
{
if (ex.InnerExceptions[0] is FirebaseException inner && (inner.ErrorCode != 0))
AddToInformation("\nError code = " + inner.ErrorCode + " Message = " + inner.Message);
}
else
{
AddToInformation("Sign In Successful.");
}
});
}
public void OnSignInSilently()
{
GoogleSignIn.Configuration = configuration;
GoogleSignIn.Configuration.UseGameSignIn = false;
GoogleSignIn.Configuration.RequestIdToken = true;
AddToInformation("Calling SignIn Silently");
GoogleSignIn.DefaultInstance.SignInSilently().ContinueWith(OnAuthenticationFinished);
}
public void OnGamesSignIn()
{
GoogleSignIn.Configuration = configuration;
GoogleSignIn.Configuration.UseGameSignIn = true;
GoogleSignIn.Configuration.RequestIdToken = false;
AddToInformation("Calling Games SignIn");
}
private void AddToInformation(string str) { infoText.text += "\n" + str; }
}
Firebase automatically persists the user's authentication state, and tried to restore it when the app restarts. But since this requires a call to the servers, it may take some time, you'll need to listen to the AuthStateChanged event as shown in the documentation on getting the currently signed in user:
Firebase.Auth.FirebaseAuth auth;
Firebase.Auth.FirebaseUser user;
// Handle initialization of the necessary firebase modules:
void InitializeFirebase() {
Debug.Log("Setting up Firebase Auth");
auth = Firebase.Auth.FirebaseAuth.DefaultInstance;
auth.StateChanged += AuthStateChanged;
AuthStateChanged(this, null);
}
// Track state changes of the auth object.
void AuthStateChanged(object sender, System.EventArgs eventArgs) {
if (auth.CurrentUser != user) {
bool signedIn = user != auth.CurrentUser && auth.CurrentUser != null;
if (!signedIn && user != null) {
Debug.Log("Signed out " + user.UserId);
}
user = auth.CurrentUser;
if (signedIn) {
Debug.Log("Signed in " + user.UserId);
}
}
}
void OnDestroy() {
auth.StateChanged -= AuthStateChanged;
auth = null;
}
Now when the app reloads, your AuthStateChanged will immediately be called with no current user, and then it will/may be called again once the user's authentication state has been restored.

Google Play in-app Billing onPurchasesUpdated() error response code -1

I've been implementing for the first time in-app billing in my app and even if all the code is correct, it is not working!
I have a BillingManager.java
public class BillingManager implements PurchasesUpdatedListener {
private static final String TAG = "BillingManager";
private final BillingClient mBillingClient;
private final Activity mActivity;
String base64Key = "mykey";
private static Context myCxt;
private String mAdRemovalPrice;
private static final String ITEM_SKU_ADREMOVAL = "myskuid";
public int billingResult;
public BillingManager(Activity activity) {
mActivity = activity;
mBillingClient = BillingClient.newBuilder(mActivity).setListener(this).build();
mBillingClient.startConnection(new BillingClientStateListener() {
#Override
public void onBillingSetupFinished(#BillingClient.BillingResponse int billingResponse) {
if (billingResponse == BillingClient.BillingResponse.OK) {
Log.i(TAG, "onBillingSetupFinished() good response: " + billingResponse);
List skuList = new ArrayList<>();
skuList.add(ITEM_SKU_ADREMOVAL);
SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder();
params.setSkusList(skuList).setType(BillingClient.SkuType.INAPP);
mBillingClient.querySkuDetailsAsync(params.build(),
new SkuDetailsResponseListener() {
#Override
public void onSkuDetailsResponse(int responseCode, List skuDetailsList) {
// Process the result.
if (responseCode == BillingClient.BillingResponse.OK
&& skuDetailsList != null) {
for (Object skuDetailsObject : skuDetailsList) {
SkuDetails skuDetails = (SkuDetails) skuDetailsObject;
String sku = skuDetails.getSku();
String price = skuDetails.getPrice();
if (ITEM_SKU_ADREMOVAL.equals(sku)) {
mAdRemovalPrice = price;
}
}
}
}
});
} else {
Log.w(TAG, "onBillingSetupFinished() error code: " + billingResponse);
}
}
#Override
public void onBillingServiceDisconnected() {
Log.w(TAG, "onBillingServiceDisconnected()");
}
});
}
#Override
public void onPurchasesUpdated(int responseCode, List<Purchase> purchases) {
if (responseCode == BillingClient.BillingResponse.OK
&& purchases != null) {
for(Purchase purchase: purchases) {
// When every a new purchase is made
// Here we verify our purchase
Log.i(TAG, "onPurchasesUpdated() ourchase ok response: " + responseCode);
if (!verifyValidSignature(purchase.getOriginalJson(), purchase.getSignature())) {
// Invalid purchase
// show error to user
myCxt = MainActivity.proContext;
Toast.makeText(myCxt, myCxt.getString(R.string.purchase_err), Toast.LENGTH_LONG).show();
Log.i(TAG, "Got a purchase: " + purchase + "; but signature is bad. Skipping...");
return;
} else {
// purchase is valid
// Perform actions
myCxt = MainActivity.proContext;
Toast.makeText(myCxt, myCxt.getString(R.string.purchase_done), Toast.LENGTH_LONG).show();
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(myCxt);
SharedPreferences.Editor editor = preferences.edit();
editor.putBoolean("isPro", true);
editor.apply();
}
}
} else if (responseCode == BillingClient.BillingResponse.USER_CANCELED) {
// Handle an error caused by a user cancelling the purchase flow.
Log.i(TAG, "onPurchasesUpdated() user canceled response: " + responseCode);
} else {
// Handle any other error codes.
Log.i(TAG, "onPurchasesUpdated() error response: " + responseCode);
}
}
public void startPurchaseFlow() {
BillingFlowParams flowParams = BillingFlowParams.newBuilder()
.setSku(ITEM_SKU_ADREMOVAL)
.setType(BillingClient.SkuType.INAPP)
.build();
mBillingClient.launchBillingFlow(mActivity, flowParams);
Log.i(TAG, "StartPurchaseFlow called");
}
private boolean verifyValidSignature(String signedData, String signature) {
try {
return Security.verifyPurchase(base64Key, signedData, signature);
} catch (IOException e) {
Log.e(TAG, "Got an exception trying to validate a purchase: " + e);
return false;
}
}
And then i call it like this in my App menu:
if (id == R.id.action_pro) {
BillingManager mbilling = new BillingManager(MainActivity.this);
mbilling.startPurchaseFlow();
return true;
}
Actually it turns out that if I read the logs in debugging mode seems that onPurchasesUpdated() method throws the error -1 as response code! So this means that the responsecode is -1 which according to Java documentation is a generic error in http protocol... Why am I getting this?
The code seems pretty good even if compared to others or to guides found online. Does anyone have any suggestions?
Please make sure your billing client is initialized before you start the purchaseflow.
response code -1 indicates billingclient disconnected

Android in app billing - How to know which users have bought a product

I have an android app with a donation system, these donations are integrated products that are automatically consumed to let the users donate more than once.
I need to know if there is some way to find out which users have donated at least once.
I appreciate any help.
EDIT:
In addition to Dima Kozhevin's answer... I used this code in onServiceConnected() event inside startSetup() method from my IabHelper.
Bundle purchaseHistoryBundle = mService.getPurchaseHistory(6,BuildConfig.APPLICATION_ID, "inapp", null, new Bundle());
ArrayList<String> mListItems = purchaseHistoryBundle.getStringArrayList("INAPP_PURCHASE_ITEM_LIST");
if (mListItems != null){
if (mListItems.size()>0){
//User have donated at least once
}else{
//User never donated
}
}
You should use getPurchaseHistory() method.
Signature of the method:
Bundle getPurchaseHistory(int apiVersion,
String packageName,
String type,
String continuationToken,
Bundle extraParams);
Your code will look like this:
Bundle purchaseHistoryBundle = service.getPurchaseHistory(6, BuildConfig.APPLICATION_ID, "subs", null, new Bundle());
In addition, guy from Google suggests in that example use queryPurchaseHistoryAsyncmethod:
This library also allows to get purchase history even though it's not
demonstrated inside the sample. Please use this method to get all
purchases history (up to 1 record per SKU).
I assume you have integrated the AIDL file and the in app billing code files for IabHelper etc.. following Android instructions for basic billing handling.
Once you are already handling billing you simply query the inventory to see if they have purchased it or not. I tend to do this in a singleton class called PurchaseManager.
I will share that class with you below. However, I only have one package, so I hard coded that package in my check for pro purchased, to be more dynamic you may want to do those checks in the calling class or in a loop.
/**
* Created by App Studio 35 on 9/28/17.
*/
public class PurchaseManager {
/*///////////////////////////////////////////////////////////////
// MEMBERS
*////////////////////////////////////////////////////////////////
private static PurchaseManager mInstance;
private static final String TAG = Globals.SEARCH_STRING + PurchaseManager.class.getSimpleName();
private static String PUBLIC_LICENSING_KEY = "<YOUR PUBLIC KEY HERE>";
private static final String PRO_PACKAGE_SKU = "pro_package_level_1";
public static final int RESULT_KEY_PURCHASE = 9876;
private IabHelper mHelper;
private Boolean mIABServiceIsAvailable = false;
private static String mAndroidId;
/*///////////////////////////////////////////////////////////////
// CONSTRUCTOR
*////////////////////////////////////////////////////////////////
private PurchaseManager(){}
public static synchronized PurchaseManager getInstance(){
if(mInstance == null){
mInstance = new PurchaseManager();
}
return mInstance;
}
/*///////////////////////////////////////////////////////////////
// EXTERNAL METHODS
*////////////////////////////////////////////////////////////////
public boolean getIsIABServiceAvailable(){
return mIABServiceIsAvailable;
}
public void checkForPurchasesOrTrials(final Context context, final IPurchaseSyncListener listener) {
mHelper = new IabHelper(context, PUBLIC_LICENSING_KEY);
if(!BuildConfig.DEBUG) {
mHelper.enableDebugLogging(true, TAG);
}
//Setup Purchase Processor
mHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() {
#Override
public void onIabSetupFinished(IabResult result) {
mIABServiceIsAvailable = result.isSuccess();
if (!result.isSuccess()) {
String error = "Problem setting up In-app Billing: " + result;
A35Log.d(TAG, error);
notifyUserOfError(listener, error);
return;
}
ArrayList<String> skus = new ArrayList<String>();
skus.add(PRO_PACKAGE_SKU);
checkExistingPurchasesForSkus(context, listener, skus);
}
});
}
public void attemptPurchaseOfPro(Activity activity, final IPurchaseConsumeListener listener){
mHelper.launchPurchaseFlow(activity, PRO_PACKAGE_SKU, RESULT_KEY_PURCHASE, new IabHelper.OnIabPurchaseFinishedListener() {
#Override
public void onIabPurchaseFinished(IabResult result, Purchase info) {
if (result.isSuccess()) {
mHelper.consumeAsync(info, new IabHelper.OnConsumeFinishedListener() {
#Override
public void onConsumeFinished(Purchase purchase, IabResult result) {
if (result.isSuccess()) {
Log.d(TAG, "Successfully synced purchases" + result);
notifyUserOfPurchaseSuccess(listener, purchase, result, PRO_PACKAGE_SKU);
} else {
String error = "Could not sync purchases. Error: " + result;
Log.d(TAG, error);
notifyUserOfPurchaseError(listener, error, result, PRO_PACKAGE_SKU);
}
}
});
}else{
notifyUserOfPurchaseError(listener, "Purchase Incomplete", result, PRO_PACKAGE_SKU);
}
}
});
}
/*///////////////////////////////////////////////////////////////
// INTERNAL METHODS
*////////////////////////////////////////////////////////////////
private void checkExistingPurchasesForSkus(final Context context, final IPurchaseSyncListener listener, final ArrayList<String> skus) {
mHelper.queryInventoryAsync(true, skus, new IabHelper.QueryInventoryFinishedListener() {
#Override
public void onQueryInventoryFinished(IabResult result, Inventory inv) {
if (!result.isSuccess()) {
String error = "Unable to query inventory. Error: " + result;
A35Log.d(TAG, error);
notifyUserOfError(listener, error);
return;
}
ArrayList<Purchase> purchaseList = new ArrayList<Purchase>();
if (inv.getPurchase(PRO_PACKAGE_SKU) != null) {
purchaseList.add(inv.getPurchase(PRO_PACKAGE_SKU));
}
if (!purchaseList.isEmpty()) {
A35Log.d(TAG, "Attempting to sync purchases" + result);
attemptToSyncPurchases(context, listener, purchaseList);
} else {
A35Log.d(TAG, "We didn't see any purchases, attempting to check for Trials");
if(mAndroidId == null) {
getAdvertiserIDThenCheckTrialsForDevice(context, listener, skus);
}else{
checkTrialsForDeviceID(context, listener, skus);
}
}
}
});
}
private void attemptToSyncPurchases(final Context context, final IPurchaseSyncListener listener, final ArrayList<Purchase> purchaseList) {
for(Purchase purchase : purchaseList) {
mHelper.consumeAsync(purchase, new IabHelper.OnConsumeFinishedListener() {
#Override
public void onConsumeFinished(Purchase purchase, IabResult result) {
if (result.isSuccess()) {
Log.d(TAG, "Successfully synced purchases" + result);
notifyUserOfPurchasedPackages(listener, purchaseList);
} else {
String error = "Could not sync purchases. Error: " + result;
Log.d(TAG, error);
notifyUserOfError(listener, error);
}
}
});
}
}
private void getAdvertiserIDThenCheckTrialsForDevice(final Context context, final IPurchaseSyncListener listener, final ArrayList<String> skus){
//If no purchases then check for trial times for skus get Advertiser ID for identifying device
new GetAdvertiserIDAsyncTask(context){
#Override
protected void onPostExecute(String advertisementID) {
mAndroidId = (advertisementID == null ? "unknownID" : advertisementID);
checkTrialsForDeviceID(context, listener, skus);
}
}.execute();
}
private void checkTrialsForDeviceID(final Context context, final IPurchaseSyncListener listener, final ArrayList<String> skus){
//Use device ID to check for Trials
new GetTrialTimeAsyncTask(context, mAndroidId){
#Override
protected void onPostExecute(ActiveTrialsListResponseModel activeTrialsListResponseModel) {
super.onPostExecute(activeTrialsListResponseModel);
A35Log.v(TAG, "onPostExecute");
if(activeTrialsListResponseModel.getErrorMessage() != null) {
String error = "Error getting trial time: " + activeTrialsListResponseModel.getErrorMessage();
A35Log.e(TAG, error);
notifyUserOfError(listener, error);
return;
}
notifyUserOfTrialCheckCompleteForPackages(listener, activeTrialsListResponseModel);
}
}.execute();
}
/*///////////////////////////////////////////////////////////////
// NOTIFY USER CALLBACKS
*////////////////////////////////////////////////////////////////
private void notifyUserOfError(IPurchaseSyncListener listener, String message){
if(listener != null){
listener.onPurchaseManagerError(message);
}
}
private void notifyUserOfPurchasedPackages(IPurchaseSyncListener listener, ArrayList<Purchase> purchasedSkus){
if(listener != null){
listener.onPackagePurchased(purchasedSkus);
}
}
private void notifyUserOfTrialCheckCompleteForPackages(IPurchaseSyncListener listener, ActiveTrialsListResponseModel activeTrialsListResponseModel){
if(listener != null){
listener.onTrialRetrievalComplete(activeTrialsListResponseModel);
}
}
private void notifyUserOfPurchaseSuccess(IPurchaseConsumeListener listener, Purchase purchase, IabResult result, String sku){
if(listener != null){
listener.onPurchaseSuccessful(purchase, result, sku);
}
}
private void notifyUserOfPurchaseError(IPurchaseConsumeListener listener, String message, IabResult result, String sku){
if(listener != null){
listener.onPurchaseFailure(message, result, sku);
}
}
/*///////////////////////////////////////////////////////////////
// INTERFACE
*////////////////////////////////////////////////////////////////
public interface IPurchaseSyncListener {
void onPackagePurchased(ArrayList<Purchase> sku);
void onTrialRetrievalComplete(ActiveTrialsListResponseModel activeTrialsListResponseModel);
void onPurchaseManagerError(String message);
}
public interface IPurchaseConsumeListener {
void onPurchaseSuccessful(Purchase purchase, IabResult result, String sku);
void onPurchaseFailure(String message, IabResult result, String sku);
}
}
Three things to note about my shared code as well.
I am using trials for my pro package so that is my async task to confirm that they are not in trials for any package, you won't do that piece.
I do not have authenticated users, I rely on the device advertiser id for knowing if they have a trial or not, this won't matter to you. Also advertiser ids can be reset by the user in Google Settings if they are crafty enough they can figure out how to get another free trial, but I'm not that concerned about the power user going that far to save a dollar haha.
I did my startup inside the checkfor purchases method because it is ONLY called one time on app startup and it is the first call. A more generic way may be to do it in the first getInstance if helper is null.
Goodluck.

Accessing property that is filled by an AsyncTask returns null

I am trying to get values from my User class (holding all the user information for the logged in user.
It is set once logged in, and it is printing out in the log just fine, but then when calling from the class that instantiates it, it returns a null? Here is the code:
public ApiConnector api;
public String ID;
public String USERNAME = null;
public String NAME = null;
public String LASTNAME = null;
public String PASSWORD = null;
public String EMAIL = null;
public User(String id) {
this.ID = id;
this.api = new ApiConnector();
new GetUserDataClass().execute(api);
}
private class GetUserDataClass extends AsyncTask<ApiConnector,Boolean,JSONArray> {
#Override
protected JSONArray doInBackground(ApiConnector... params) {
return params[0].getAllUserData(ID);
}
#Override
protected void onPostExecute(JSONArray jsonArray) {
if(jsonArray != null) {
for (int i = 0; i < jsonArray.length(); i++) {
JSONObject json = null;
try {
json = new JSONObject();
json = jsonArray.getJSONObject(i);
if(!json.getString("username").isEmpty()) {
setUsername(json.getString("username"));
Log.d("username", getUsername());
}
if(!json.getString("firstname").isEmpty()) {
setName(json.getString("firstname"));
Log.d("name", getName());
}
if(!json.getString("lastname").isEmpty()) {
setLastName(json.getString("lastname"));
Log.d("lastname", getLastName());
}
if(!json.getString("email").isEmpty()) {
setEmail(json.getString("email"));
Log.d("email", getEmail());
}
} catch (JSONException e) {
e.printStackTrace();
}
}
} else {
Log.d("hey", "hey");
}
}
The logcat output is:
05-19 02:03:55.996: W/EGL_emulation(4367): eglSurfaceAttrib not implemented
05-19 02:03:55.996: D/username(4367): Me
05-19 02:03:55.996: D/name(4367): Me
05-19 02:03:55.996: D/lastname(4367): Mememe
05-19 02:03:55.996: D/email(4367): me#example.com
I have all appropriate getters and setters in the class (as you can see in the above code, working fine.
Here is the Menu class (that is returning the null):
public class Menu extends Activity {
private String ID;
private User user;
public TextView tvusername;
#Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main_menu);
init();
}
public void init() {
Bundle bundle = getIntent().getExtras();
ID = bundle.getString("id");
user = new User(ID);
tvusername = (TextView) findViewById(R.id.tvUsername);
tvusername.setText(ID + " " + user.getEmail());
}
}
Here is what it looks like on the emulator:
I have spent the last day and a half looking for solutions, but came up empty. Please would you help?
you can use
new GetUserDataClass().execute(api).get();
so that system will wait to complete AsyncTask. and you will get the id.
You are loading user data in an AsyncTask... and that's good because it seems to perform some network operation.
It means that user data loading is performed asynchronously... and so not available immediately.
You have a callback onPostExecute in the AsyncTask : use it to update the UI with something like this code :
//you need to initialize it in the AsyncTask constrtuctor
final Activity myActivity;
#Override
protected void onPostExecute(JSONArray jsonArray) {
//json parsing code
//...
//and finally update the UI
new Handler(Looper.getMainLooper()).post(
new Runnable(){
public void run(){
TextView tvusername = (TextView) myActivity.findViewById(R.id.tvUsername);
tvusername.setText(ID + " " + getEmail());
}
}
);
}
I think you forgot to use static word in User class object creation while setting data.
other wise it will create new instance every time and that object will not show your data.
I solved it by using the Singleton Design Pattern in my User class. Just so people can look that up to solve this if they are having the same issues!
Cheers and thanks for all the replies!

Constructing the most reliable user country mechanism

In an application that I'm currently working on there is a huge need to determine user country as fast as possible and as reliable as possible. I have decided to rely on three ways for finding user country; each one has its advantages and disadvantages:
Android inner methods to get the SIM country.
GeoCoding.
IP to Location API.
Here are the three pieces of code:
1. Android inner methods to get the SIM country:
public static String getUserCountry(Context context) {
try {
final TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
final String simCountry = tm.getSimCountryIso();
if (simCountry != null && simCountry.length() == 2) { // SIM country code is available
CupsLog.d(TAG, "getUserCountry, simCountry: " + simCountry.toLowerCase(Locale.US));
return simCountry.toLowerCase(Locale.US);
}
else if (tm.getPhoneType() != TelephonyManager.PHONE_TYPE_CDMA) { // device is not 3G (would be unreliable)
String networkCountry = tm.getNetworkCountryIso();
if (networkCountry != null && networkCountry.length() == 2) { // network country code is available
CupsLog.d(TAG, "getUserCountry, networkCountry: " + networkCountry.toLowerCase(Locale.US));
return networkCountry.toLowerCase(Locale.US);
}
}
}
catch (Exception e) { }
return null;
}
2. GeoCoding:
public static void getCountryCode(final Location location, final Context context) {
CupsLog.d(TAG, "getCountryCode");
AsyncTask<Void, Void, String> countryCodeTask = new AsyncTask<Void, Void, String>() {
final float latitude = (float) location.getLatitude();
final float longitude = (float) location.getLongitude();
List<Address> addresses = null;
Geocoder gcd = new Geocoder(context);
String code = null;
#Override
protected String doInBackground(Void... params) {
CupsLog.d(TAG, "doInBackground");
try {
addresses = gcd.getFromLocation(latitude, longitude, 10);
for (int i=0; i < addresses.size(); i++)
{
if (addresses.get(i) != null && addresses.get(i).getCountryCode() != null)
{
code = addresses.get(i).getCountryCode();
}
}
} catch (IOException e) {
CupsLog.d(TAG, "IOException" + e);
}
return code;
}
#Override
protected void onPostExecute(String code)
{
if (code != null)
{
CupsLog.d(TAG, "getCountryCode :" + code.toLowerCase());
UserLocation.instance.setCountryCode(code.toLowerCase());
CookieUtil.formatLangueageAndLocationCookie();
CookieUtil.instance.instantateCookieUtil(context);
}
}
};
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD_MR1) {
CupsLog.d(TAG, "countryCodeTask.execute();");
countryCodeTask.execute();
} else {
CupsLog.d(TAG, "countryCodeTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);");
countryCodeTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
}
3. IP to Location API:
private void getUserCountryByIp() {
AsyncHttpClient client = new AsyncHttpClient();
client.setCookieStore(CookieUtil.instance.getPersistentCookieStore());
String userCountryApi = "http://ip-api.com/json";
CupsLog.i(TAG, "country uri: " + userCountryApi);
client.get(userCountryApi, new JsonHttpResponseHandler() {
#Override
public void onSuccess(JSONObject orderResponseJSON) {
CupsLog.i(TAG, "onSuccess(JSONObject res)");
try
{
CupsLog.i(TAG, "JsonResponse: "+ orderResponseJSON.toString(3));
String tempString = orderResponseJSON.getString("countryCode");
if (tempString != null)
{
//countryCodeFromIpApi = tempString.toLowerCase();
UserLocation.instance.setCountryCode(tempString.toLowerCase());
CookieUtil.formatLangueageAndLocationCookie();
CookieUtil.instance.instantateCookieUtil(LoginActivity.this);
isGotCountryFromIp = true;
}
} catch (JSONException e) {
CupsLog.i(TAG, "JSONException: " + e);
}
}
#Override
public void onFailure(Throwable arg0, JSONObject orderResponseJSON) {
CupsLog.i(TAG, "onFailure");
try {
CupsLog.i(TAG, "JsonResponse: "+ orderResponseJSON.toString(3));
} catch (JSONException e) {
CupsLog.i(TAG, "JSONException: " + e);
}
}
#Override
public void onFinish() {
CupsLog.i(TAG, "onFinish");
super.onFinish();
}
});
}
Now I have those 3 methods that are working great, my problem is more of a Java problem. The first method give me the result right away, while the two others (2,3) are potentiality long running tasks. what more is that the first option is the most not reliable one, as users can travel to different countries with the SIM card. The second is more reliable but still sometimes does not returns an country (depending on the location of the user). The third one is the one that I found to be the most reliable one, but the most long as well.
The question: knowing this information, how would I construct a method that uses those 3 methods in the right order for reliability stand point and considering the long running tasks factor?
Unfortunately there is no really reliable way to determine the physical location of a user (e.g. his/her cellphone) because:
SIM card might be bought and/or manufactured in other country;
Geocoding (which is AFAIU based on GPS/GLONASS coordinates) might give wrong (~10m) results or no results at all if user disabled it or no satellites are visible (underground, for example);
Resolving country by IP might also give you wrong results, for example because of using VPN;
So my advice would be to ask user, which country he is currently in and willing to "tell" you so.

Categories

Resources