Skip to content

Commit 98a6d48

Browse files
ren6Vladimir Slatvinski
andauthored
Feature/2.8.0 (#97)
* Defer Placements method * Flush User Properties method * Does not automatically load permission groups at launch * Automatic purchases restore at launch * Can purchase by ProductDetails ID without calling for permission groups * Improvements for User Properties APIs --------- Co-authored-by: Vladimir Slatvinski <[email protected]>
1 parent 749e80b commit 98a6d48

25 files changed

+380
-218
lines changed

demo_old/src/main/java/com/apphud/demo/ApphudApplication.kt

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import android.util.Log
66
import androidx.lifecycle.lifecycleScope
77
import com.apphud.sdk.Apphud
88
import com.apphud.sdk.ApphudError
9+
import com.apphud.sdk.ApphudUserProperty
10+
import com.apphud.sdk.ApphudUserPropertyKey
911
import com.apphud.sdk.ApphudUtils
1012
import com.apphud.sdk.client.ApiClient
1113
import com.apphud.sdk.domain.ApphudPaywall
@@ -20,15 +22,14 @@ import kotlin.coroutines.resume
2022

2123
class ApphudApplication : Application() {
2224
var API_KEY = "YOUR_API_KEY"
23-
2425
companion object {
2526
private lateinit var instance: ApphudApplication
2627

2728
fun applicationContext(): Context {
2829
return instance.applicationContext
2930
}
3031

31-
fun application(): Application {
32+
fun application(): ApphudApplication {
3233
return instance
3334
}
3435
}
@@ -40,16 +41,10 @@ class ApphudApplication : Application() {
4041
private val applicationScope = CoroutineScope(Dispatchers.Default)
4142

4243
var attempt = 0
43-
4444
override fun onCreate() {
4545
super.onCreate()
46-
if (BuildConfig.DEBUG) {
47-
ApphudUtils.enableDebugLogs()
48-
}
49-
5046
Apphud.start(this, API_KEY, observerMode = false)
5147
Apphud.collectDeviceIdentifiers()
52-
5348
fetchPlacements()
5449
}
5550

demo_old/src/main/java/com/apphud/demo/MainActivity.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.apphud.demo
22

33
import android.os.Bundle
4+
import android.util.Log
45
import android.widget.Toast
56
import androidx.appcompat.app.AppCompatActivity
67
import androidx.appcompat.widget.Toolbar
@@ -11,7 +12,11 @@ import androidx.navigation.ui.AppBarConfiguration
1112
import androidx.navigation.ui.navigateUp
1213
import androidx.navigation.ui.setupActionBarWithNavController
1314
import com.apphud.demo.databinding.ActivityMainBinding
15+
import com.apphud.sdk.Apphud
1416
import com.google.android.material.navigation.NavigationView
17+
import kotlinx.coroutines.CoroutineScope
18+
import kotlinx.coroutines.Dispatchers
19+
import kotlinx.coroutines.launch
1520

1621
class MainActivity : AppCompatActivity() {
1722
private lateinit var appBarConfiguration: AppBarConfiguration

demo_old/src/main/java/com/apphud/demo/ui/customer/CustomerFragment.kt

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import androidx.recyclerview.widget.DividerItemDecoration
1414
import androidx.recyclerview.widget.RecyclerView
1515
import com.android.billingclient.api.ProductDetails
1616
import com.android.billingclient.api.Purchase
17+
import com.apphud.demo.ApphudApplication
1718
import com.apphud.demo.BuildConfig
1819
import com.apphud.demo.databinding.FragmentCustomerBinding
1920
import com.apphud.sdk.Apphud
@@ -50,8 +51,7 @@ class CustomerFragment : Fragment() {
5051
binding.appVersion.text = BuildConfig.VERSION_NAME + " (" + BuildConfig.VERSION_CODE + ")"
5152

5253
binding.btnSync.setOnClickListener {
53-
Apphud.restorePurchases { subscriptions, purchases, error ->
54-
}
54+
Apphud.restorePurchases { _, _, _ -> }
5555
}
5656

5757
paywallsViewModel = ViewModelProvider(this)[PaywallsViewModel::class.java]
@@ -120,27 +120,11 @@ class CustomerFragment : Fragment() {
120120
}
121121
Apphud.setListener(listener)
122122

123-
getPaywalls()
124123
updateData()
125124

126125
return root
127126
}
128127

129-
private fun getPaywalls() {
130-
Apphud.fetchPlacements { plms, error ->
131-
if (plms.isEmpty() && Apphud.isFallbackMode()) {
132-
val paywall = Apphud.rawPaywalls().firstOrNull()
133-
paywall?.let {
134-
val fallbackPlacement = ApphudPlacement.createCustom("fallback", paywall = it)
135-
Log.d("ApphudLogs", "FALLBACK PLACEMENT: ${fallbackPlacement}")
136-
}
137-
}
138-
if (error != null) {
139-
Log.d("ApphudLogs", "Placements fetch error: ${error.billingErrorTitle()}")
140-
}
141-
}
142-
}
143-
144128
private fun updateData() {
145129
_binding?.isPremiumValue?.text = if (Apphud.hasPremiumAccess()) "Premium" else "No Premium"
146130
lifecycleScope.launch {

demo_old/src/main/java/com/apphud/demo/ui/customer/PaywallsViewModel.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ class PaywallsViewModel : ViewModel() {
1616
var showPlacements: Boolean = false
1717

1818
suspend fun updateData() {
19-
Log.d("ApphudLogs", "PaywallsViewModel update data")
2019
if (showPlacements) {
2120
items.clear()
2221
val placements = Apphud.rawPlacements()

demo_old/src/main/java/com/apphud/demo/ui/groups/GroupsAdapter.kt

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,12 @@ import android.view.ViewGroup
77
import android.widget.TextView
88
import androidx.recyclerview.widget.RecyclerView
99
import com.apphud.demo.R
10+
import com.apphud.sdk.Apphud
1011
import com.apphud.sdk.domain.ApphudGroup
11-
import com.apphud.sdk.domain.ApphudProduct
12-
import com.apphud.sdk.domain.ApphudProductType
1312

1413
class GroupsAdapter(private val groupsViewModel: GroupsViewModel, private val context: Context?) : RecyclerView.Adapter<GroupsAdapter.BaseViewHolder<*>>() {
1514
var selectGroup: ((account: ApphudGroup) -> Unit)? = null
16-
var selectProduct: ((account: ApphudProduct) -> Unit)? = null
15+
var selectProductId: ((account: String) -> Unit)? = null
1716

1817
abstract class BaseViewHolder<T>(itemView: View) : RecyclerView.ViewHolder(itemView) {
1918
abstract fun bind(
@@ -36,26 +35,27 @@ class GroupsAdapter(private val groupsViewModel: GroupsViewModel, private val co
3635
}
3736
}
3837

39-
inner class ApphudProductViewHolder(itemView: View) : BaseViewHolder<ApphudProduct>(itemView) {
38+
inner class ApphudProductIdViewHolder(itemView: View) : BaseViewHolder<String>(itemView) {
4039
private val productName: TextView = itemView.findViewById(R.id.productName)
4140
private val productId: TextView = itemView.findViewById(R.id.productId)
4241
private val productPrice: TextView = itemView.findViewById(R.id.productPrice)
4342

4443
override fun bind(
45-
item: ApphudProduct,
44+
item: String,
4645
position: Int,
4746
) {
48-
productName.text = item.name
49-
productId.text = item.productId
47+
val productDetails = Apphud.product(item)
48+
productName.text = productDetails?.name
49+
productId.text = productDetails?.productId
5050

51-
if (item.type() == ApphudProductType.SUBS) {
52-
productPrice.text = item.subscriptionOfferDetails()?.firstOrNull()?.pricingPhases?.pricingPhaseList?.firstOrNull()?.formattedPrice ?: ""
51+
if (productDetails?.productType?.lowercase() == "subs") {
52+
productPrice.text = productDetails?.subscriptionOfferDetails?.firstOrNull()?.pricingPhases?.pricingPhaseList?.firstOrNull()?.formattedPrice ?: ""
5353
} else {
54-
productPrice.text = item.oneTimePurchaseOfferDetails()?.formattedPrice ?: ""
54+
productPrice.text = productDetails?.oneTimePurchaseOfferDetails?.formattedPrice ?: ""
5555
}
5656

5757
itemView.setOnClickListener {
58-
selectProduct?.invoke(item)
58+
selectProductId?.invoke(item)
5959
}
6060
}
6161
}
@@ -80,7 +80,7 @@ class GroupsAdapter(private val groupsViewModel: GroupsViewModel, private val co
8080
val view =
8181
LayoutInflater.from(parent.context)
8282
.inflate(R.layout.list_item_product, parent, false)
83-
ApphudProductViewHolder(view)
83+
ApphudProductIdViewHolder(view)
8484
}
8585
else -> throw IllegalArgumentException("Invalid view type")
8686
}
@@ -93,15 +93,15 @@ class GroupsAdapter(private val groupsViewModel: GroupsViewModel, private val co
9393
val element = groupsViewModel.items[position]
9494
when (holder) {
9595
is ApphudGroupViewHolder -> holder.bind(element as ApphudGroup, position)
96-
is ApphudProductViewHolder -> holder.bind(element as ApphudProduct, position)
96+
is ApphudProductIdViewHolder -> holder.bind(element as String, position)
9797
else -> throw IllegalArgumentException()
9898
}
9999
}
100100

101101
override fun getItemViewType(position: Int): Int {
102102
return when (groupsViewModel.items[position]) {
103103
is ApphudGroup -> TYPE_GROUP
104-
is ApphudProduct -> TYPE_PRODUCT
104+
is String -> TYPE_PRODUCT
105105
else -> throw IllegalArgumentException("Invalid type of data " + position)
106106
}
107107
}

demo_old/src/main/java/com/apphud/demo/ui/groups/GroupsFragment.kt

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,17 @@ import android.widget.Toast
88
import androidx.fragment.app.Fragment
99
import androidx.lifecycle.ViewModelProvider
1010
import androidx.recyclerview.widget.RecyclerView
11+
import com.apphud.demo.R
1112
import com.apphud.demo.databinding.FragmentGroupsBinding
13+
import com.apphud.demo.ui.utils.BaseFragment
14+
import com.apphud.sdk.Apphud
15+
import com.apphud.sdk.domain.ApphudPaywall
16+
import kotlinx.coroutines.Dispatchers
17+
import kotlinx.coroutines.coroutineScope
18+
import kotlinx.coroutines.launch
19+
import kotlinx.coroutines.withContext
1220

13-
class GroupsFragment : Fragment() {
21+
class GroupsFragment : BaseFragment() {
1422
private lateinit var groupsViewModel: GroupsViewModel
1523
private lateinit var viewAdapter: GroupsAdapter
1624
private var _binding: FragmentGroupsBinding? = null
@@ -31,8 +39,14 @@ class GroupsFragment : Fragment() {
3139
viewAdapter.selectGroup = {
3240
Toast.makeText(activity, it.name, Toast.LENGTH_SHORT).show()
3341
}
34-
viewAdapter.selectProduct = { product ->
35-
// Do nothing here
42+
viewAdapter.selectProductId = { product ->
43+
Apphud.purchase(requireActivity(), product) { result ->
44+
result.error?.let { err ->
45+
Toast.makeText(activity, if (result.userCanceled()) "User Canceled" else err.message, Toast.LENGTH_SHORT).show()
46+
} ?: run {
47+
Toast.makeText(activity, R.string.success, Toast.LENGTH_SHORT).show()
48+
}
49+
}
3650
}
3751

3852
val recyclerView: RecyclerView = binding.groupsList
@@ -51,7 +65,12 @@ class GroupsFragment : Fragment() {
5165
}
5266

5367
private fun updateData() {
54-
groupsViewModel.updateData()
68+
coroutineScope.launch {
69+
groupsViewModel.updateData()
70+
mainScope.launch {
71+
viewAdapter.notifyDataSetChanged()
72+
}
73+
}
5574
}
5675

5776
override fun onDestroyView() {

demo_old/src/main/java/com/apphud/demo/ui/groups/GroupsViewModel.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@ import com.apphud.sdk.Apphud
66
class GroupsViewModel : ViewModel() {
77
var items = mutableListOf<Any>()
88

9-
fun updateData() {
10-
val list = Apphud.permissionGroups()
9+
suspend fun updateData() {
10+
val list = Apphud.fetchPermissionGroups()
1111
items.clear()
1212

1313
list.forEach {
1414
items.add(it)
15-
it.products?.let { productsList ->
15+
it.productIds().let { productsList ->
1616
items.addAll(productsList)
1717
}
1818
}

demo_old/src/main/java/com/apphud/demo/ui/products/ProductsFragment.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ class ProductsFragment : Fragment() {
127127
if (placementId != null) {
128128
Apphud.placements().firstOrNull { it.identifier == placementId }?.paywall
129129
} else {
130-
Apphud.paywalls().firstOrNull { it.identifier == paywallId }
130+
Apphud.rawPaywalls().firstOrNull { it.identifier == paywallId }
131131
}
132132
return paywall
133133
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package com.apphud.demo.ui.utils
2+
3+
import android.util.Log
4+
import androidx.fragment.app.Fragment
5+
import kotlinx.coroutines.CoroutineExceptionHandler
6+
import kotlinx.coroutines.CoroutineScope
7+
import kotlinx.coroutines.Dispatchers
8+
import kotlinx.coroutines.SupervisorJob
9+
import org.greenrobot.eventbus.EventBus
10+
import org.greenrobot.eventbus.Subscribe
11+
import org.greenrobot.eventbus.ThreadMode
12+
13+
class BaseEvent
14+
15+
open class BaseFragment : Fragment() {
16+
17+
val mainScope = CoroutineScope(Dispatchers.Main)
18+
val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
19+
val errorHandler = CoroutineExceptionHandler { _, error ->
20+
error.message?.let {
21+
Log.d("BaseFragment", it)
22+
}
23+
}
24+
25+
override fun onStart() {
26+
super.onStart()
27+
EventBus.getDefault().register(this)
28+
}
29+
30+
override fun onStop() {
31+
EventBus.getDefault().unregister(this)
32+
super.onStop()
33+
}
34+
35+
@Subscribe(threadMode = ThreadMode.MAIN)
36+
fun onEvent(event: BaseEvent) {
37+
38+
}
39+
}

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,4 @@ android.useAndroidX=true
1919
android.enableJetifier=true
2020
# Kotlin code style for this project: "official" or "obsolete":
2121
kotlin.code.style=official
22-
sdkVersion=2.7.4
22+
sdkVersion=2.8.0

0 commit comments

Comments
 (0)