{"action":"created","issue":{"url":"https://api.github.com/repos/onepf/OpenIAB/issues/469","labels_url":"https://api.github.com/repos/onepf/OpenIAB/issues/469/labels{/name}","comments_url":"https://api.github.com/repos/onepf/OpenIAB/issues/469/comments","events_url":"https://api.github.com/repos/onepf/OpenIAB/issues/469/events","html_url":"https://github.com/onepf/OpenIAB/issues/469","id":64912699,"number":469,"title":"Problems with offline mode","user":{"login":"Lakedaemon","id":103175,"avatar_url":"https://avatars.githubusercontent.com/u/103175?v=3","gravatar_id":"","url":"https://api.github.com/users/Lakedaemon","html_url":"https://github.com/Lakedaemon","followers_url":"https://api.github.com/users/Lakedaemon/followers","following_url":"https://api.github.com/users/Lakedaemon/following{/other_user}","gists_url":"https://api.github.com/users/Lakedaemon/gists{/gist_id}","starred_url":"https://api.github.com/users/Lakedaemon/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Lakedaemon/subscriptions","organizations_url":"https://api.github.com/users/Lakedaemon/orgs","repos_url":"https://api.github.com/users/Lakedaemon/repos","events_url":"https://api.github.com/users/Lakedaemon/events{/privacy}","received_events_url":"https://api.github.com/users/Lakedaemon/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":2,"created_at":"2015-03-28T07:21:31Z","updated_at":"2015-03-28T09:41:28Z","closed_at":null,"body":"I have been using openIAB for a while now and implemented it through fragments\r\nlots users have made purchases through Google play\r\nsome through Amazon\r\nIt works quite well\r\n\r\nYet, 3 of my users have reported that it doesn't work so great in offline mode.\r\nLuckily I'm able to reproduce the issue they have on my phone : \r\n\r\nBasically, after a reboot in offline mode, the purchase is recognized and I can use the app in premium mode a while yet if I change the screen orientation or if I exit the app and reenter it, the purchase is not recognized anymore, I gor an error code 6 (error refreshing inventory (querying prices of items))\r\n\r\nThis lasts till I go out of offline mode... then the purchase is recognized instantly and all goes well\r\n\r\nThis is quite a big problem for my app, because when it goes from free to premium or from premium to free, (which is supposed to happen only once, when a purchase is made) there is a lot of computation and wait for the user (30s) because the data cached on disk has to be processed and changed...\r\n\r\nAnd also, if premium features aren't recognized, these calculation happen at each app start :/\r\nThis makes it quire horrible to use (and I get some bad reviews)\r\n\r\n\r\nI think that the issue might come from 3 places  :\r\n1) google play doesn't cache it's purchase inventory after a reboot in offline mode or it is corrupt (very unlickely)\r\n2) openIAB fails at some point in reboot in offline mode after a configuration change/relaunch and if some static variable has a corrupt value, it keeps on doing so while in offline mode afterwards\r\n3) my code fails in reboot in offline mode too\r\n\r\nI'm using openIAB 0.9.8.6+\r\n\r\nHere is my fragment (kotlin code, versy similar to java)\r\n\r\n\r\nfinal class OpenBillingFragment() : Fragment(), IabHelper.OnIabSetupFinishedListener, IabHelper.QueryInventoryFinishedListener, IabHelper.OnIabPurchaseFinishedListener {\r\n   \r\n    /** Create the helper, passing it our context\r\n     * and the public key to verify signatures with */\r\n    private var helper: OpenIabHelper? = null\r\n\r\n    /** errors */\r\n    private val errors = ArrayList<String>()\r\n\r\n    fun addError(string: String) {\r\n        if (errors.size() >= 5) errors.remove(0)\r\n        errors.add(string)\r\n        L.info(string)\r\n    }\r\n\r\n    fun addError(e: Exception) = addError(e.format())\r\n\r\n\r\n    fun debug() :String =\"\"\"helper : ${helper != null}\r\n    installer ${appContext.getPackageManager().getInstallerPackageName(App.dPackageName)}\r\n    setup : ${helper?.setupSuccessful()} (${helper?.getSetupState() ?: \"\"})\r\n    connected store : ${helper?.getConnectedAppstoreName() ?: \"\"}\r\n    \"\"\"\r\n\r\n\r\n    var launchBuyAfterSetup = false\r\n\r\n\r\n    final override fun onCreate(savedInstanceState: Bundle?) {\r\n        super<Fragment>.onCreate(savedInstanceState)\r\n        setHasOptionsMenu(true)\r\n        setRetainInstance(true)\r\n    }\r\n\r\n\r\n\r\n    override fun onAttach(activity: Activity?) {\r\n        super<Fragment>.onAttach(activity)\r\n        helper = OpenIabHelper(activity, InAppBuyD.options)\r\n        try {\r\n            L.info(\"loggable ${Logger.isLoggable()}\")\r\n            helper?.startSetup(this)\r\n        } catch (e: Exception) {\r\n            L.info(\"setup fail_1\")\r\n            addError(e)\r\n            addToAcra(KEY_OB_ATTACH, e)\r\n        } // binding the service\r\n    }\r\n\r\n    final override fun onIabSetupFinished(result: IabResult?): Unit = if (result?.isSuccess() ?: false) {\r\n        try {\r\n            L.info(\"setup success\")\r\n            helper?.queryInventoryAsync(this)\r\n        } catch (e: Exception) {\r\n            addError(C.res(R.string.inAppBuyUnavailable) + e.format())\r\n            addToAcra(KEY_OB_SETUP_FINISHED, e)\r\n            asyncUpdateMenu()\r\n        }\r\n    } else {\r\n        L.info(\"setup fail\")\r\n        addError(C.res(R.string.failedToSetupBillingToast) + \"\\n\" + result.toString() +\"\\n\" +debug())\r\n        addToAcra(KEY_OB_SETUP_FINISHED, message = \"bad setup<br>\" + result.toString()+\"<br>\"+debug())\r\n        asyncUpdateMenu()\r\n    }\r\n\r\n    fun asyncUpdateMenu(): Unit = Observable.just(Unit)\r\n            .subscribeOn(Schedulers.immediate())\r\n            .observeOn(AndroidSchedulers.mainThread())\r\n            .subscribe({ getSupportActivity()?.supportInvalidateOptionsMenu() }, { L.info(\"updateMenu\", it) }).toUnit()\r\n\r\n    override fun onQueryInventoryFinished(result: IabResult?, inventory: Inventory?) = Observable.just(Unit)\r\n            .subscribeOn(Schedulers.immediate())\r\n            .observeOn(AndroidSchedulers.mainThread())\r\n            .subscribe({\r\n                val oldValue = Skus.isDictPremium\r\n                Skus.isDictPremium = if ( inventory == null || (result?.isFailure() ?: true)) {\r\n                    addToAcra(KEY_INVENTORY_FINISHED, message = \"result:$result inventory:$inventory\")\r\n                    getSupportActivity()?.uiShowToast(C.res(R.string.purchaseStatusError) + (if (result == null) \"\" else \"\\n\" + result.getMessage()))\r\n                    false\r\n                } else inventory.getPurchase(Skus.PREMIUM_FEATURES_0)?.verify() ?: false\r\n\r\n                addToAcra(KEY_IS_PREMIUM, message = \"$oldValue -> ${Skus.isDictPremium}\")\r\n                getSupportActivity()?.supportInvalidateOptionsMenu()\r\n\r\n                handleBuildChange(oldValue)\r\n\r\n                if (launchBuyAfterSetup) {// even for premium\r\n                    launchBuyAfterSetup = false\r\n                    uiLaunchPurchaseFlow()\r\n                }\r\n            }, { L.info(\"onQuerryFinished\", it) }).toUnit()\r\n\r\n\r\n    private fun handleBuildChange(oldValue:Boolean) :Unit = if (oldValue != Skus.isDictPremium) startbuildForcefullyD() else startbuildIfNecessary()\r\n\r\n    fun handleBillingResultFromActivity(requestCode: Int, resultCode: Int, data: Intent?):Boolean {\r\n        val resultBoolean = helper?.handleActivityResult(requestCode, resultCode, data)\r\n        when (resultBoolean) {\r\n            true -> L.info(\"onActivityResult handled by IABUtil.\")\r\n            else -> {\r\n                //if (requestCode != REQUEST_CODE) samsung inApp billing uses other request code\r\n                addError(\"InAppBuyActivity Returned with result : \" + resultCode + data.toString())\r\n\r\n                // not handled, so handle it ourselves (here's where you'd\r\n                // perform any handling of activity results not related to in-app\r\n                // billing...\r\n                super<Fragment>.onActivityResult(requestCode, resultCode, data);\r\n            }\r\n        }\r\n        asyncUpdateMenu()\r\n        return resultBoolean ?: false\r\n    }\r\n\r\n    final override fun onCreateOptionsMenu(menu: Menu?, inflater: MenuInflater?) {\r\n        inflater?.inflate(R.menu.billing, menu)\r\n        super<Fragment>.onCreateOptionsMenu(menu, inflater)\r\n    }\r\n\r\n    final override fun onPrepareOptionsMenu(menu: Menu?) {\r\n        val notBuilt = getSupportFragmentManager()?.findFragmentByTag(RxActivity.WATCHER) != null\r\n        menu?.findItem(R.id.menuErrors)?.setVisible(!notBuilt && !Skus.isDictPremium && errors.size() > 0)\r\n        menu?.findItem(R.id.menuBuyPremium)?.setVisible(!notBuilt && !Skus.isDictPremium)\r\n        menu?.findItem(R.id.menuCheckAppLicense)?.setVisible(false)\r\n        super<Fragment>.onPrepareOptionsMenu(menu)\r\n    }\r\n\r\n    final override fun onOptionsItemSelected(item: MenuItem?): Boolean = when (item!!.getItemId()) {\r\n        R.id.menuBuyPremium -> {\r\n            uiLaunchPurchaseFlow()\r\n            true\r\n        }\r\n        R.id.menuErrors -> {\r\n            val activity = getSupportActivity()\r\n            val inflater = LayoutInflater.from(activity)\r\n            val convertView = inflater.inflate(R.layout.listview, null)\r\n            val lv = convertView?.findViewById(android.R.id.list) as? ListView\r\n            lv?.setAdapter(ArrayAdapter(activity, android.R.layout.simple_list_item_1, errors))\r\n            AlertDialog.Builder(activity).setView(convertView).setTitle(R.string.object_Errors)\r\n                    .setPositiveButton(R.string.actionClear, fun(d: DialogInterface, r: Int) {\r\n                        errors.clear()\r\n                        getSupportActivity()?.supportInvalidateOptionsMenu()\r\n                        d.dismiss()\r\n                    })\r\n                    .show()\r\n            true\r\n        }\r\n        else -> super<Fragment>.onOptionsItemSelected(item)\r\n    }\r\n\r\n    fun uiLaunchPurchaseFlow() :Unit = if (!Skus.isDictPremium) try {\r\n        if (helper?.setupSuccessful() == true) helper?.launchPurchaseFlow(getSupportActivity(), Skus.PREMIUM_FEATURES_0, REQUEST_CODE, this, PAYLOAD)\r\n        else getSupportActivity()?.uiShowToast(R.string.failedToSetupBillingToast)\r\n    } catch (e: Exception) {\r\n        addToAcra(KEY_OB_DISPLAY, e)\r\n        addError(e)\r\n        getSupportActivity()?.uiShowToast(getString(R.string.inAppErrorToast) + \"\\n\" + e.getMessage())\r\n    }\r\n\r\n    final override fun onIabPurchaseFinished(result: IabResult?, purchase: Purchase?): Unit = Observable.just(Unit)\r\n            .subscribeOn(Schedulers.immediate())\r\n            .observeOn(AndroidSchedulers.mainThread())\r\n            .subscribe({\r\n                val oldValue = Skus.isDictPremium\r\n                val message: String =\r\n                        if (result != null && result.isSuccess() && purchase != null && purchase.getSku() == Skus.PREMIUM_FEATURES_0) {\r\n                            // TODO report sku change (for downloading files)\r\n                            Skus.isDictPremium = purchase.verify()\r\n\r\n                            when (purchase.getPurchaseState()) {\r\n                                0 -> C.res(R.string.purchasePurchased)\r\n                                1 -> C.res(R.string.purchaseCanceled)\r\n                                else -> C.res(R.string.purchaseRefounded)\r\n                            }\r\n                        } else {\r\n                            addToAcra(KEY_OB_PURCHASE_ERROR, message = (result?.getMessage()?:\"result \") + (purchase?.toString() ?:\"purchase \"))\r\n                            C.res(R.string.purchaseError) + if (result == null) \"\" else \"\\n\" + result.getMessage()\r\n                        }\r\n                addToAcra(KEY_IS_PREMIUM, message = \"$oldValue -> ${Skus.isDictPremium}\")\r\n                getSupportActivity()?.uiShowToast(message)\r\n                getSupportActivity()?.supportInvalidateOptionsMenu()\r\n                handleBuildChange(oldValue)\r\n            }, { L.info(\"toast Error\", it) }).toUnit()\r\n\r\n    final override fun onDetach() {\r\n        try {\r\n            helper?.dispose()\r\n            helper = null\r\n        } catch (e: Exception) {\r\n            addToAcra(KEY_OB_DETACH, e)\r\n            L.info(\"onDetach error\", e)\r\n        }\r\n        super<Fragment>.onDetach()\r\n    }\r\n\r\n    fun FragmentActivity.uiShowToast(charSequence: CharSequence, isLong: Boolean = false): Unit = if (isResumed() && !isFinishing()) Toast.makeText(this, charSequence, if (isLong) Toast.LENGTH_LONG else Toast.LENGTH_SHORT)?.show()\r\n    fun FragmentActivity.uiShowToast(resId: Int, isLong: Boolean = false): Unit = uiShowToast(getString(resId), isLong)\r\n\r\n    companion object {\r\n        val KEY_OB_ATTACH = \"OB:onAttach\"\r\n        val KEY_OB_SETUP_FINISHED = \"OB:setupFinished\"\r\n        val KEY_INVENTORY_FINISHED = \"OB:inventoryFinished\"\r\n        val KEY_OB_RESULT = \"OB:activityResult\"\r\n        val KEY_OB_DISPLAY = \"OB:display\"\r\n        val KEY_OB_DETACH = \"OB:detach\"\r\n        val KEY_OB_PURCHASE_ERROR = \"OB:purchaseError\"\r\n        val KEY_IS_PREMIUM = \"OB:isPremium\"\r\n\r\n        private fun Purchase.verify(): Boolean = getDeveloperPayload() == PAYLOAD && getPurchaseState() == 0\r\n    }\r\n\r\n\r\n\r\nand I have this in my activity : \r\n    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {\r\n        val fragment = getSupportFragmentManager().findFragmentByTag(LicensingFragment.TAG)\r\n        if (fragment !is OpenBillingFragment || !fragment.handleBillingResultFromActivity(requestCode, resultCode, data)) super<ActionBarActivity>.onActivityResult(requestCode, resultCode, data)\r\n    }\r\n\r\n"},"comment":{"url":"https://api.github.com/repos/onepf/OpenIAB/issues/comments/87198081","html_url":"https://github.com/onepf/OpenIAB/issues/469#issuecomment-87198081","issue_url":"https://api.github.com/repos/onepf/OpenIAB/issues/469","id":87198081,"user":{"login":"Lakedaemon","id":103175,"avatar_url":"https://avatars.githubusercontent.com/u/103175?v=3","gravatar_id":"","url":"https://api.github.com/users/Lakedaemon","html_url":"https://github.com/Lakedaemon","followers_url":"https://api.github.com/users/Lakedaemon/followers","following_url":"https://api.github.com/users/Lakedaemon/following{/other_user}","gists_url":"https://api.github.com/users/Lakedaemon/gists{/gist_id}","starred_url":"https://api.github.com/users/Lakedaemon/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Lakedaemon/subscriptions","organizations_url":"https://api.github.com/users/Lakedaemon/orgs","repos_url":"https://api.github.com/users/Lakedaemon/repos","events_url":"https://api.github.com/users/Lakedaemon/events{/privacy}","received_events_url":"https://api.github.com/users/Lakedaemon/received_events","type":"User","site_admin":false},"created_at":"2015-03-28T09:41:28Z","updated_at":"2015-03-28T09:41:28Z","body":"I rebooted in offline mode and could use the app for a long while in premium mode\r\n(I could create a lot of activities without my OpenBillingFragment, all was fine, including configuration change -> no openIAB calls there)...\r\nThen...I pressed back... to my main activity (with an OpenBillingFragment) and left the app.\r\nNext time, I opened the app and the issue was here.\r\n\r\nApparently, the issu happens when I have the 3 following items  \r\n1) Rebooting in offline mode (the issue doesn't happen if you go into offline mode after having been online)\r\n2) destroying phase of an activity with my OpenBillingFragment (as the starting phase went well) \r\n3)  staying in Offline mode (going online fixes the issue)  \r\n"}}