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
Related
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.
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.
Currently using open source project (https://github.com/mcollinge/07kit/tree/master/src/main)
And when i build the application, it looks like this: http://prntscr.com/f2zh5a
The thing is, the API is on their side (public static final String API_URL = "https://api.somesite.com/user";)
Their API is down. Therefore i cannot register on their site, to go through the logging in process.
Would there be any way to bypass login?
Providing LoginController class code below.
public class LoginController extends Controller<LoginView> {
public static final String API_URL = "";
private final Logger logger = Logger.getLogger(LoginController.class);
private LoginView view;
public LoginController() {
ControllerManager.add(LoginController.class, this);
}
public void show() {
try {
if (Session.get().getApiToken() != null) {
logger.info("Existing API token found - trying to retrieve account info...");
if (loadAccount(Session.get().getApiToken(), true, Session.get().getEmail().getValue())) {
logger.info("Logged in with pre-existing key.");
return;
}
}
} catch (Exception e) {
logger.error("Failed to authenticate.", e);
}
Toolkit toolkit = Toolkit.getDefaultToolkit();
int centerX = (toolkit.getScreenSize().width / 2) - (getComponent().getWidth() / 2);
int centerY = (toolkit.getScreenSize().height / 2) - (getComponent().getHeight() / 2);
getComponent().setLocation(centerX, centerY);
getComponent().setIconImage(Application.ICON_IMAGE);
getComponent().setVisible(true);
}
public void login(String email, String password, boolean rememberMe) {
try {
SwingWorker worker = new SwingWorker() {
#Override
protected Object doInBackground() throws Exception {
HttpResponse response = Executor.newInstance(HttpUtil.getClient()).execute(Request.Post(API_URL + "/token")
.bodyString(JacksonUtil.serialize(new CreateTokenRequest(email, password)), ContentType.APPLICATION_JSON)).returnResponse();
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
UserToken tokenResponse = JacksonUtil.deserialize(
EntityUtils.toString(response.getEntity()),
UserToken.class);
if (loadAccount(tokenResponse.getUuid(), rememberMe, email)) {
logger.info("Logged in.");
return null;
}
}
logger.error("Invalid login, response: [" + response.toString() + "]");
getComponent().getStatusLbl().setText("Status: Invalid login");
return null;
}
};
worker.execute();
} catch (Exception e) {
logger.error("Oops.", e);
getComponent().getStatusLbl().setText("Status: Error logging in");
}
}
private boolean loadAccount(String uuid, boolean rememberMe, String email) throws IOException {
HttpResponse getAccountResponse = Executor.newInstance(HttpUtil.getClient()).execute(Request.Get(API_URL)
.addHeader("Authorization", "Bearer " + uuid)).returnResponse();
if (getAccountResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
logger.info("Processed login [" + getAccountResponse.toString() + "]");
UserAccount account = JacksonUtil.deserialize(
EntityUtils.toString(getAccountResponse.getEntity()),
UserAccount.class);
if (account != null && account.getStatus() == UserAccount.Status.ACTIVE &&
account.getType() != null) {
getComponent().getStatusLbl().setText("Status: Logged in");
getComponent().dispose();
Session.get().setUserAccount(account);
Session.get().setApiToken(uuid);
Property emailProperty = Session.get().getEmail();
Property apiKeyProperty = Session.get().getApiKey();
if (rememberMe) {
if (emailProperty == null) {
emailProperty = new Property(Session.EMAIL_PROPERTY_KEY, email);
emailProperty.save();
} else {
emailProperty.setValue(email);
emailProperty.save();
}
if (apiKeyProperty == null) {
apiKeyProperty = new Property(Session.API_KEY_PROPERTY_KEY, uuid);
apiKeyProperty.save();
} else {
apiKeyProperty.setValue(uuid);
apiKeyProperty.save();
}
} else {
if (emailProperty != null) {
emailProperty.remove();
}
if (apiKeyProperty != null) {
apiKeyProperty.remove();
}
}
Session.get().onAuthenticated();
ControllerManager.get(MainController.class).show();
return true;
} else {
getComponent().getStatusLbl().setText("Status: Only BETA users can login");
return false;
}
}
return false;
}
#Override
public LoginView getComponent() {
if (view == null) {
view = new LoginView(this);
}
return view;
}
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*/
};
}
Note:This is not a duplication or spammming since i have checked whole lot of searches.
Im currently developing an gcm integrated application and when i run on my emulator,it shows :
04-13 00:07:23.814: W/ActivityManager(366): Unable to start service
Intent { act=com.google.android.c2dm.intent.REGISTER
pkg=com.google.android.gms (has extras) } U=0: not found
My code is not similar to searches in here as I followed this tutorial https://github.com/erikswed/InstaChatX
In this tutorial,there are only 4 gcm client class,gcmbroadcast receiver,Gcm Util,Server Utilities and Constants.In those 4 classes havent mentioned about intent and in android manifest files also dont have permission:
Here is code for 4 gcm client class:
public class GcmBroadcastReceiver extends BroadcastReceiver {
private static final String TAG = "GcmBroadcastReceiver";
private Context ctx;
#Override
public void onReceive(Context context, Intent intent) {
ctx = context;
PowerManager mPowerManager = (PowerManager)
context.getSystemService(Context.POWER_SERVICE);
WakeLock mWakeLock =
mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
mWakeLock.acquire();
try {
GoogleCloudMessaging gcm =
GoogleCloudMessaging.getInstance(context);
String messageType = gcm.getMessageType(intent);
if
(GoogleCloudMessaging.MESSAGE_TYPE_SEND_ERROR.equals(messageType)) {
sendNotification("Send error", false);
} else if
(GoogleCloudMessaging.MESSAGE_TYPE_DELETED.equals(messageType)) {
sendNotification("Deleted messages on server", false);
} else {
String msg = intent.getStringExtra(DataProvider.COL_MESSAGE);
String senderEmail =
intent.getStringExtra(DataProvider.COL_SENDER_EMAIL);
String receiverEmail =
intent.getStringExtra(DataProvider.COL_RECEIVER_EMAIL);
ContentValues values = new ContentValues(2);
values.put(DataProvider.COL_TYPE,
MessageType.INCOMING.ordinal());
values.put(DataProvider.COL_MESSAGE, msg);
values.put(DataProvider.COL_SENDER_EMAIL, senderEmail);
values.put(DataProvider.COL_RECEIVER_EMAIL, receiverEmail);
context.getContentResolver().insert
(DataProvider.CONTENT_URI_MESSAGES, values);
if (Common.isNotify()) {
sendNotification("New message", true);
}
}
setResultCode(Activity.RESULT_OK);
} finally {
mWakeLock.release();
}
}
private void sendNotification(String text, boolean launchApp) {
NotificationManager mNotificationManager = (NotificationManager)
ctx.getSystemService(Context.NOTIFICATION_SERVICE);
NotificationCompat.Builder notification = new
NotificationCompat.Builder(ctx);
notification.setContentTitle(ctx.getString(R.string.app_name));
notification.setContentText(text);
notification.setAutoCancel(true);
notification.setSmallIcon(R.drawable.ic_launcher);
if (!TextUtils.isEmpty(Common.getRingtone())) {
notification.setSound(Uri.parse(Common.getRingtone()));
}
if (launchApp) {
Intent intent = new Intent(ctx, Chat_List.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
Intent.FLAG_ACTIVITY_SINGLE_TOP);
PendingIntent pi = PendingIntent.getActivity(ctx, 0, intent,
PendingIntent.FLAG_UPDATE_CURRENT);
notification.setContentIntent(pi);
}
mNotificationManager.notify(1, notification.build());
}
}
This is GcmUtil class:
public class GcmUtil {
private static final String TAG = "GcmUtil";
public static final String PROPERTY_REG_ID = "registration_id";
private static final String PROPERTY_APP_VERSION = "appVersion";
private static final String PROPERTY_ON_SERVER_EXPIRATION_TIME = "onServerExpirationTimeMs";
/**
* Default lifespan (7 days) of a reservation until it is considered expired.
*/
public static final long REGISTRATION_EXPIRY_TIME_MS = 1000 * 3600 * 24 * 7;
private static final int MAX_ATTEMPTS = 5;
private static final int BACKOFF_MILLI_SECONDS = 2000;
private static final Random random = new Random();
private Context ctx;
private SharedPreferences prefs;
private GoogleCloudMessaging gcm;
private AsyncTask registrationTask;
public GcmUtil(Context ApplicationContext) {
super();
ctx = ApplicationContext;
prefs = PreferenceManager.getDefaultSharedPreferences(ctx);
String regid = getRegistrationId();
if (regid.length() == 0) {
registerBackground();
} else {
broadcastStatus(true);
}
gcm = GoogleCloudMessaging.getInstance(ctx);
}
/**
* Gets the current registration id for application on GCM service.
* <p>
* If result is empty, the registration has failed.
*
* #return registration id, or empty string if the registration is not
* complete.
*/
private String getRegistrationId() {
String registrationId = prefs.getString(PROPERTY_REG_ID, "");
if (registrationId.length() == 0) {
//Log.v(TAG, "Registration not found.");
return "";
}
// check if app was updated; if so, it must clear registration id to
// avoid a race condition if GCM sends a message
int registeredVersion = prefs.getInt(PROPERTY_APP_VERSION, Integer.MIN_VALUE);
int currentVersion = getAppVersion();
if (registeredVersion != currentVersion || isRegistrationExpired()) {
//Log.v(TAG, "App version changed or registration expired.");
return "";
}
return registrationId;
}
/**
* Stores the registration id, app versionCode, and expiration time in the
* application's {#code SharedPreferences}.
*
* #param regId registration id
*/
private void setRegistrationId(String regId) {
int appVersion = getAppVersion();
//Log.v(TAG, "Saving regId on app version " + appVersion);
SharedPreferences.Editor editor = prefs.edit();
editor.putString(PROPERTY_REG_ID, regId);
editor.putInt(PROPERTY_APP_VERSION, appVersion);
long expirationTime = System.currentTimeMillis() + REGISTRATION_EXPIRY_TIME_MS;
//Log.v(TAG, "Setting registration expiry time to " + new Timestamp(expirationTime));
editor.putLong(PROPERTY_ON_SERVER_EXPIRATION_TIME, expirationTime);
editor.commit();
}
/**
* #return Application's version code from the {#code PackageManager}.
*/
private int getAppVersion() {
try {
PackageInfo packageInfo = ctx.getPackageManager().getPackageInfo(ctx.getPackageName(), 0);
return packageInfo.versionCode;
} catch (NameNotFoundException e) {
// should never happen
throw new RuntimeException("Could not get package name: " + e);
}
}
/**
* Checks if the registration has expired.
*
* <p>To avoid the scenario where the device sends the registration to the
* server but the server loses it, the app developer may choose to re-register
* after REGISTRATION_EXPIRY_TIME_MS.
*
* #return true if the registration has expired.
*/
private boolean isRegistrationExpired() {
// checks if the information is not stale
long expirationTime = prefs.getLong(PROPERTY_ON_SERVER_EXPIRATION_TIME, -1);
return System.currentTimeMillis() > expirationTime;
}
/**
* Registers the application with GCM servers asynchronously.
* <p>
* Stores the registration id, app versionCode, and expiration time in the
* application's shared preferences.
*/
private void registerBackground() {
registrationTask = new AsyncTask<Void, Void, Boolean>() {
#Override
protected Boolean doInBackground(Void... params) {
long backoff = BACKOFF_MILLI_SECONDS + random.nextInt(1000);
for (int i = 1; i <= MAX_ATTEMPTS; i++) {
//Log.d(TAG, "Attempt #" + i + " to register");
try {
if (gcm == null) {
gcm = GoogleCloudMessaging.getInstance(ctx);
}
String regid = gcm.register(Common.getSenderId());
// You should send the registration ID to your server over HTTP,
// so it can use GCM/HTTP or CCS to send messages to your app.
ServerUtilities.register(Common.getPreferredEmail(), regid);
// Save the regid - no need to register again.
setRegistrationId(regid);
return Boolean.TRUE;
} catch (IOException ex) {
//Log.e(TAG, "Failed to register on attempt " + i + ":" + ex);
if (i == MAX_ATTEMPTS) {
break;
}
try {
//Log.d(TAG, "Sleeping for " + backoff + " ms before retry");
Thread.sleep(backoff);
} catch (InterruptedException e1) {
// Activity finished before we complete - exit.
//Log.d(TAG, "Thread interrupted: abort remaining retries!");
Thread.currentThread().interrupt();
}
// increase backoff exponentially
backoff *= 2;
}
}
return Boolean.FALSE;
}
#Override
protected void onPostExecute(Boolean status) {
broadcastStatus(status);
}
}.execute();
}
private void broadcastStatus(boolean status) {
Intent intent = new Intent(Common.ACTION_REGISTER);
intent.putExtra(Common.EXTRA_STATUS, status ? Common.STATUS_SUCCESS : Common.STATUS_FAILED);
ctx.sendBroadcast(intent);
}
public void cleanup() {
if (registrationTask != null) {
registrationTask.cancel(true);
}
if (gcm != null) {
gcm.close();
}
}
}
This is Server Utilities Class:
public final class ServerUtilities {
private static final String TAG = "ServerUtilities";
private static final int MAX_ATTEMPTS = 5;
private static final int BACKOFF_MILLI_SECONDS = 2000;
private static final Random random = new Random();
/**
* Register this account/device pair within the server.
*/
public static void register(final String email, final String regId) {
//Log.i(TAG, "registering device (regId = " + regId + ")");
String serverUrl = Common.getServerUrl() + "/register";
Map<String, String> params = new HashMap<String, String>();
params.put(DataProvider.SENDER_EMAIL, email);
params.put(DataProvider.REG_ID, regId);
// Once GCM returns a registration id, we need to register it in the
// demo server. As the server might be down, we will retry it a couple
// times.
try {
post(serverUrl, params, MAX_ATTEMPTS);
} catch (IOException e) {
}
}
/**
* Unregister this account/device pair within the server.
*/
public static void unregister(final String email) {
//Log.i(TAG, "unregistering device (email = " + email + ")");
String serverUrl = Common.getServerUrl() + "/unregister";
Map<String, String> params = new HashMap<String, String>();
params.put(DataProvider.SENDER_EMAIL, email);
try {
post(serverUrl, params, MAX_ATTEMPTS);
} catch (IOException e) {
// At this point the device is unregistered from GCM, but still
// registered in the server.
// We could try to unregister again, but it is not necessary:
// if the server tries to send a message to the device, it will get
// a "NotRegistered" error message and should unregister the device.
}
}
/**
* Send a message.
*/
public static void send(String msg, String to) throws IOException {
//Log.i(TAG, "sending message (msg = " + msg + ")");
String serverUrl = Common.getServerUrl() + "/send";
Map<String, String> params = new HashMap<String, String>();
params.put(DataProvider.MESSAGE, msg);
params.put(DataProvider.SENDER_EMAIL, Common.getPreferredEmail());
params.put(DataProvider.RECEIVER_EMAIL, to);
post(serverUrl, params, MAX_ATTEMPTS);
}
/** Issue a POST with exponential backoff */
private static void post(String endpoint, Map<String, String> params, int maxAttempts) throws IOException {
long backoff = BACKOFF_MILLI_SECONDS + random.nextInt(1000);
for (int i = 1; i <= maxAttempts; i++) {
//Log.d(TAG, "Attempt #" + i);
try {
post(endpoint, params);
return;
} catch (IOException e) {
//Log.e(TAG, "Failed on attempt " + i + ":" + e);
if (i == maxAttempts) {
throw e;
}
try {
Thread.sleep(backoff);
} catch (InterruptedException e1) {
Thread.currentThread().interrupt();
return;
}
backoff *= 2;
} catch (IllegalArgumentException e) {
throw new IOException(e.getMessage(), e);
}
}
}
/**
* Issue a POST request to the server.
*
* #param endpoint POST address.
* #param params request parameters.
*
* #throws IOException propagated from POST.
*/
private static void post(String endpoint, Map<String, String> params) throws IOException {
URL url;
try {
url = new URL(endpoint);
} catch (MalformedURLException e) {
throw new IllegalArgumentException("invalid url: " + endpoint);
}
StringBuilder bodyBuilder = new StringBuilder();
Iterator<Entry<String, String>> iterator = params.entrySet().iterator();
// constructs the POST body using the parameters
while (iterator.hasNext()) {
Entry<String, String> param = iterator.next();
bodyBuilder.append(param.getKey()).append('=').append(param.getValue());
if (iterator.hasNext()) {
bodyBuilder.append('&');
}
}
String body = bodyBuilder.toString();
//Log.v(TAG, "Posting '" + body + "' to " + url);
byte[] bytes = body.getBytes();
HttpURLConnection conn = null;
try {
conn = (HttpURLConnection) url.openConnection();
conn.setDoOutput(true);
conn.setUseCaches(false);
conn.setFixedLengthStreamingMode(bytes.length);
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8");
// post the request
OutputStream out = conn.getOutputStream();
out.write(bytes);
out.close();
// handle the response
int status = conn.getResponseCode();
if (status != 200) {
throw new IOException("Post failed with error code " + status);
}
} finally {
if (conn != null) {
conn.disconnect();
}
}
}
}
Constants class only have sender id and url.