just wondering if its possible to call suspend fun handlePurchase() inside:
suspend fun handlePurchase(purchase: Purchase) {
if (purchase.purchaseState == Purchase.PurchaseState.PURCHASED) {
if (!purchase.isAcknowledged) {
val acknowledgePurchaseParams = AcknowledgePurchaseParams.newBuilder()
.setPurchaseToken(purchase.purchaseToken)
val ackPurchaseResult = withContext(Dispatchers.IO) {
val client = BillingClient.newBuilder(this#GoproActivity).build()
client.acknowledgePurchase(acknowledgePurchaseParams.build())
}
}
}
}
private val purchasesUpdatedListener = PurchasesUpdatedListener { billingResult, purchases ->
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK && purchases != null) {
for (purchase in purchases) {
handlePurchase(purchase) --->> here is the problem
}
} else if (billingResult.responseCode == BillingClient.BillingResponseCode.USER_CANCELED) {
// Handle an error caused by a user cancelling the purchase flow.
}
}
inside onCreate:
var billingClient = BillingClient.newBuilder(this)
.setListener(purchasesUpdatedListener)
.enablePendingPurchases().build()
val skuList = ArrayList<String>()
skuList.add("dons.dogs.04")
button.setOnClickListener {
billingClient.startConnection(object : BillingClientStateListener {
override fun onBillingSetupFinished(billingResult: BillingResult) {
// TODO("Not yet implemented")
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
val params = SkuDetailsParams.newBuilder()
params.setSkusList(skuList)
.setType(BillingClient.SkuType.INAPP)
billingClient.querySkuDetailsAsync(params.build()) { billingResult, skuDetailsList ->
for (skuDetails in skuDetailsList!!) {
val flowPurchase = BillingFlowParams.newBuilder()
.setSkuDetails(skuDetails)
.build()
val responseCode = billingClient.launchBillingFlow(
this#GoproActivity,
flowPurchase
).responseCode
}
}
}
}
override fun onBillingServiceDisconnected() {
TODO("Not yet implemented")
}
})
}
Everything should be Ok after that. Until now purchases are happening but canceled/refunded because are not acknowledged. I tried to follow different tutorials and resources also the official documentation but dont understund how to implement this part here. As i said the "purchasing" part works properly but can not acknowledge them. Is there any easier way to do that?
Thanks in advance.
You can use,
lifeCycleScope.launch{
//your suspend function here
}
Related
I am trying to develop a football app demo. Data comes from an API from the api
It loads data as expected when app started, but when score of match changes, ui is not updating for scores by itself. I am using DiffUtil getChangePayload() to detect changes in score and status fields of Match objects which comes from the response. But it is not triggering when live match data changes. What am i missing?
P.S. I put layout in SwipeRefreshLayout and when i refresh, it gets scores and update the ui. But i want to see the match status and scores updating by itself.
Here is my code:
class MatchesViewModel(
app: Application,
private val repository: MatchesRepository
): AndroidViewModel(app) {
val matchesToday: MutableLiveData<List<Matche>> = MutableLiveData()
init {
getMatchesToday()
}
fun getMatchesToday() = viewModelScope.launch {
safeMatchesToday()
}
private suspend fun safeMatchesToday() {
if (Constants.checkConnection(this)) {
val response = repository.getMatchesToday()
if (response.isSuccessful) {
response.body()?.let {
matchesToday.postValue(it.matches)
}
}
}
}
}
class MatchesTodayFragment : Fragment() {
private var _binding: FragmentMatchesTodayBinding? =null
private val binding get() = _binding!!
private lateinit var mMatchesAdapter: MatchesAdapter
private val viewModel: MatchesViewModel by viewModels {
MatchesViewModelFactory(requireActivity().application, (requireActivity().application as MatchesApplication).repository)
}
#RequiresApi(Build.VERSION_CODES.N)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupRecyclerView()
viewModel.matchesToday.observe(viewLifecycleOwner) { matches ->
mMatchesAdapter.differ.submitList(matches)
}
binding.srlMatchesToday.setOnRefreshListener {
viewModel.getMatchesToday()
binding.srlMatchesToday.isRefreshing = false
}
}
}
class MatchesAdapter(val fragment: Fragment): RecyclerView.Adapter<MatchesAdapter.ViewHolder>() {
private val differCallback = object: DiffUtil.ItemCallback<Matche>() {
override fun areItemsTheSame(oldItem: Matche, newItem: Matche): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: Matche, newItem: Matche): Boolean {
return oldItem.status == newItem.status &&
oldItem.score.fullTime.home == newItem.score.fullTime.home &&
oldItem.score.fullTime.away == newItem.score.fullTime.away &&
oldItem == newItem
}
override fun getChangePayload(oldItem: Matche, newItem: Matche): Any? {
val bundle: Bundle = bundleOf()
if (oldItem.status != newItem.status) {
bundle.apply {
putString(Constants.MATCH_STATUS, newItem.status)
}
}
if (oldItem.score.fullTime.home != newItem.score.fullTime.home) {
bundle.apply {
putInt(Constants.HOME_SCORE, newItem.score.fullTime.home)
}
}
if (oldItem.score.fullTime.away != newItem.score.fullTime.away) {
bundle.apply {
putInt(Constants.AWAY_SCORE, newItem.score.fullTime.away)
}
}
if (bundle.size() == 0) {
return null
}
return bundle
}
}
val differ = AsyncListDiffer(this, differCallback)
#SuppressLint("UseCompatLoadingForDrawables")
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val match = differ.currentList[position]
holder.apply {
Glide.with(fragment)
.load(match.homeTeam.crest)
.placeholder(fragment.resources.getDrawable(R.drawable.ic_ball))
.into(ivHomeTeamImage)
Glide.with(fragment)
.load(match.awayTeam.crest)
.placeholder(fragment.resources.getDrawable(R.drawable.ic_ball))
.into(ivAwayTeamImage)
tvHomeTeamName.text = match.homeTeam.name
tvAwayTeamName.text = match.awayTeam.name
when (match.status) {
Constants.TIMED -> {
tvMatchTime.text = Constants.toTimeForTR(match.utcDate)
tvHomeTeamScore.text = "-"
tvAwayTeamScore.text = "-"
}
Constants.PAUSED -> {
tvMatchTime.text = Constants.FIRST_HALF
tvHomeTeamScore.text = match.score.fullTime.home.toString()
tvAwayTeamScore.text = match.score.fullTime.away.toString()
}
Constants.FINISHED -> {
tvMatchTime.text = Constants.FINISHED
tvHomeTeamScore.text = match.score.fullTime.home.toString()
tvAwayTeamScore.text = match.score.fullTime.away.toString()
}
else -> {
tvMatchTime.text = Constants.IN_PLAY
tvHomeTeamScore.text = match.score.fullTime.home.toString()
tvAwayTeamScore.text = match.score.fullTime.away.toString()
}
}
}
}
override fun onBindViewHolder(holder: ViewHolder, position: Int, payloads: MutableList<Any>) {
if (payloads.isNotEmpty()) {
val item = payloads[0] as Bundle
val status = item.getString(Constants.MATCH_STATUS)
val homeScore = item.getInt(Constants.HOME_SCORE)
val awayScore = item.getInt(Constants.AWAY_SCORE)
holder.apply {
tvMatchTime.text = status
tvHomeTeamScore.text = homeScore.toString()
tvAwayTeamScore.text = awayScore.toString()
Log.e("fuck", status.toString())
}
}
super.onBindViewHolder(holder, position, payloads)
}
override fun getItemCount(): Int {
return differ.currentList.size
}
}
LiveData only pushes new values if you command it to. Since you want to do it repeatedly, you need to create a loop. This is very easy to do using the liveData coroutine builder.
class MatchesViewModel(
app: Application,
private val repository: MatchesRepository
): AndroidViewModel(app) {
val matchesToday = liveData {
while (true) {
if (Constants.checkConnection(this)) {
val response = repository.getMatchesToday()
if (response.isSuccessful) {
response.body()?.let {
emit(it.matches)
}
}
}
delay(5000) // however many ms you want between fetches
}
}
}
If this is a Retrofit response, I think checking isSuccessful is redundant because body() will be non-null if and only if isSuccessful is true. So it could be simplified a bit from what you have:
class MatchesViewModel(
app: Application,
private val repository: MatchesRepository
): AndroidViewModel(app) {
val matchesToday = liveData {
while (true) {
if (Constants.checkConnection(this)) {
repository.getMatchesToday()?.body()?.matches?.let(::emit)
}
delay(5000) // however many ms you want between fetches
}
}
}
hi guys I got an error like the one below when trying to upload an image from the galley:
java.lang.SecurityException: Permission Denial: opening provider com.android.providers.media.MediaDocumentsProvider from ProcessRecord{efed650 15275:app.fadlyproject.com/u0a158} (pid=15275, uid=10158) requires that you obtain access using ACTION_OPEN_DOCUMENT or related APIs
the error comes from this line of code:
val parcelFileDescriptor =
contentResolver.openFileDescriptor(selectedImageUri!!, "r", null) ?: return
I'm using 3rd party libraries namely DrJacky and Retrofit 2. I've added some necessary things to the manifest as below:
Dependencies :
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.github.Drjacky:ImagePicker:2.3.19'
Manifest :
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
image_view = findViewById(R.id.image_view)
button_upload = findViewById(R.id.button_upload)
image_view!!.setOnClickListener {
openImageChooser()
}
button_upload!!.setOnClickListener {
uploadImage()
}
}
private val profileLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == Activity.RESULT_OK) {
val uri = it.data?.data!!
selectedImageUri = uri
image_view!!.setImageURI(selectedImageUri)
} else parseError(it)
}
private fun openImageChooser() {
ImagePicker.with(this)
.provider(ImageProvider.BOTH)
.setDismissListener {
Log.d("ImagePicker", "onDismiss");
}
.createIntentFromDialog { profileLauncher.launch(it) }
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == Activity.RESULT_OK) {
when (requestCode) {
REQUEST_CODE_PICK_IMAGE -> {
selectedImageUri = data?.data
image_view!!.setImageURI(selectedImageUri)
}
}
}
}
private fun uploadImage() {
if (selectedImageUri == null) {
layout_root!!.snackbar("Select an Image First")
return
}
val parcelFileDescriptor =
contentResolver.openFileDescriptor(selectedImageUri!!, "r", null) ?: return
val inputStream = FileInputStream(parcelFileDescriptor.fileDescriptor)
val file = File(cacheDir, contentResolver.getFileName(selectedImageUri!!))
val outputStream = FileOutputStream(file)
inputStream.copyTo(outputStream)
progress_bar!!.progress = 0
val body = UploadRequestBody(file, "image", this)
MyAPI().uploadImage(
MultipartBody.Part.createFormData(
"file",
file.name,
body
),
RequestBody.create(MediaType.parse("multipart/form-data"), "json")
).enqueue(object : Callback<UploadResponse> {
override fun onFailure(call: Call<UploadResponse>, t: Throwable) {
layout_root!!.snackbar(t.message!!)
progress_bar!!.progress = 0
}
override fun onResponse(
call: Call<UploadResponse>,
response: Response<UploadResponse>
) {
response.body()?.let {
layout_root!!.snackbar(it.message)
progress_bar!!.progress = 100
classes!!.text = it.data.classes
layout!!.visibility = View.VISIBLE
Glide.with(this#MainActivity).load("http://167.172.72.26:1337/"+ it.data.image_after_preprocessing).into(
image_view!!
)
}
}
})
}
override fun onProgressUpdate(percentage: Int) {
progress_bar!!.progress = percentage
}
companion object {
const val REQUEST_CODE_PICK_IMAGE = 101
}
This answer is late but it might help others.
I just added .crop() to it like this.
ImagePicker.with(this)
.crop()
.provider(ImageProvider.BOTH)
.setDismissListener {
Log.d("ImagePicker", "onDismiss");
}
.createIntentFromDialog { profileLauncher.launch(it) }
I noticed that the error only persist when using the gallery and adding the .crop() is the only solution to it.
In case you don't use Crop option, you just need to add:
mGalleryUri?.let { galleryUri ->
contentResolver.takePersistableUriPermission(
galleryUri, Intent.FLAG_GRANT_READ_URI_PERMISSION
)
}
https://developer.android.com/training/data-storage/shared/photopicker#persist-media-file-access
I'll change ACTION_GET_CONTENT to ACTION_OPEN_DOCUMENT on the next update.
[And if I could find a way to have both worlds(having crop and let developer use or not use takePersistableUriPermission, I'll update again.]
var responseMap = mutableMapOf<VendorType, ChargeResponse>()
requests.forEach {
val response = when (it.vendorType) {
VendorType.Type1 -> service.chargeForType1()
VendorType.Type2 -> service.chargeForType2()
else -> {
throw NotImplementedError("${it.vendorType} does not support yet")
}
}
responseMap[it.vendorType] = response
}
responseMap
So I want all the service.charge function run in separate thread. Return the map when all is done
Hope to solve your problem:
Assume your service and request like this:
interface Service {
suspend fun chargeForType1(): ChargeResponse
suspend fun chargeForType2(): ChargeResponse
}
data class Request(val vendorType: VendorType)
suspend fun requestAll(requests: List<Request>): Map<VendorType, ChargeResponse> {
return coroutineScope {
requests
.map { request ->
async {
request.vendorType to when (request.vendorType) {
VendorType.Type1 -> service.chargeForType1()
VendorType.Type2 -> service.chargeForType2()
else -> throw NotImplementedError("${request.vendorType} does not support yet")
}
}
}
.awaitAll()
.toMap()
}
}
I have the following problem that I am already working on for over 20 hours: I want to use an Event Channel to get a data stream from the Spotify SDK. On the native side, I can automatically display the status of a current song by subscribing to my PlayerState. My goal is to be able to access this data stream with my Flutter app. On the native side I can output the data flow without problems. But I also want to be able to access this data in my Flutter App. The problem is that I do not get the data from Kotlin to Dart. I can not execute the command mEventSink?.success(position) because the mEventSink is zero.
It would be really great if someone could help me with this problem.
//...
class Spotifysdk04Plugin(private var registrar: Registrar): MethodCallHandler, EventChannel.StreamHandler {
//...
private var mEventSink: EventChannel.EventSink? = null
companion object {
#JvmStatic
fun registerWith(registrar: Registrar) {
val channel = MethodChannel(registrar.messenger(), "spotifysdk")
channel.setMethodCallHandler(Spotifysdk04Plugin(registrar))
val eventChannel = EventChannel(registrar.messenger(), "timerStream")
eventChannel.setStreamHandler(Spotifysdk04Plugin(registrar))
}
}
override fun onMethodCall(call: MethodCall, result: Result) {
if (call.method == "loginAppRemote") {
//...
} else if(call.method == "initEventStream") {
try {
spotifyAppRemote!!.playerApi.subscribeToPlayerState()
.setEventCallback { playerState: PlayerState? ->
Log.d("test", "test24")
var position = playerState!!.playbackPosition.toDouble()
Log.d("playbackPosition1", position.toString())
if(mEventSink != null) {
Log.d("test", "test25")
mEventSink?.success(position)
} else {
Log.d("test", "mEventSink == null")
}
}
} catch (err:Throwable) {
Log.v("initEventStreamError",err.message.toString())
result.success(false)
}
} else {
result.notImplemented()
}
}
override fun onCancel(arguments: Any?) {
mEventSink = null
}
override fun onListen(arguments: Any?, eventSink: EventChannel.EventSink) {
mEventSink = eventSink
}
}
I found a solution:
override fun onListen(p0: Any?, p1: EventChannel.EventSink?) {
mEventSink = p1
Log.d("test", "test1")
if(spotifyAppRemote == null) {
Log.d("test", "test2")
}
val connectionParams = ConnectionParams.Builder(clientId)
.setRedirectUri(redirectUri)
.showAuthView(true)
.build()
SpotifyAppRemote.connect(registrar.context(), connectionParams, object : Connector.ConnectionListener {
override fun onConnected(appRemote: SpotifyAppRemote) {
spotifyAppRemote = appRemote
if(spotifyAppRemote != null) {
Log.d("test", "test3")
spotifyAppRemote!!.playerApi.subscribeToPlayerState()
.setEventCallback { playerState: PlayerState? ->
Log.d("test", "test24")
var position = playerState!!.playbackPosition.toDouble()
Log.d("playbackPosition1", position.toString())
if(mEventSink != null) {
Log.d("test", "test25")
mEventSink?.success(position)
} else {
Log.d("test", "mEventSink == null")
}
}
}
Log.d("Spotify App Remote Login", "Connected!")
}
override fun onFailure(throwable: Throwable) {
Log.e("Spotify App Remote Login", "Error!", throwable)
}
})
}
Android Studio 3.0 Beta2
I have created 2 methods one that creates the observable and another that creates the subscriber.
However, I am having a issue try to get the subscriber to subscribe to the observable. In Java this would work, and I am trying to get it to work in Kotlin.
In my onCreate(..) method I am trying to set this. Is this the correct way to do this?
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
/* CANNOT SET SUBSCRIBER TO SUBCRIBE TO THE OBSERVABLE */
createStringObservable().subscribe(createStringSubscriber())
}
fun createStringObservable(): Observable<String> {
val myObservable: Observable<String> = Observable.create {
subscriber ->
subscriber.onNext("Hello, World!")
subscriber.onComplete()
}
return myObservable
}
fun createStringSubscriber(): Subscriber<String> {
val mySubscriber = object: Subscriber<String> {
override fun onNext(s: String) {
println(s)
}
override fun onComplete() {
println("onComplete")
}
override fun onError(e: Throwable) {
println("onError")
}
override fun onSubscribe(s: Subscription?) {
println("onSubscribe")
}
}
return mySubscriber
}
}
Many thanks for any suggestions,
pay close attention to the types.
Observable.subscribe() has three basic variants:
one that accepts no arguments
several that accept an io.reactivex.functions.Consumer
one that accepts an io.reactivex.Observer
the type you're attempting to subscribe with in your example is org.reactivestreams.Subscriber (defined as part of the Reactive Streams Specification). you can refer to the docs to get a fuller accounting of this type, but suffice to say it's not compatible with any of the overloaded Observable.subscribe() methods.
here's a modified example of your createStringSubscriber() method that will allow your code to compile:
fun createStringSubscriber(): Observer<String> {
val mySubscriber = object: Observer<String> {
override fun onNext(s: String) {
println(s)
}
override fun onComplete() {
println("onComplete")
}
override fun onError(e: Throwable) {
println("onError")
}
override fun onSubscribe(s: Disposable) {
println("onSubscribe")
}
}
return mySubscriber
}
the things changed are:
this returns an Observer type (instead of Subscriber)
onSubscribe() is passed a Disposable (instead of Subscription)
.. and as mentioned by 'Vincent Mimoun-Prat', lambda syntax can really shorten your code.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Here's an example using pure RxJava 2 (ie not using RxKotlin)
Observable.create<String> { emitter ->
emitter.onNext("Hello, World!")
emitter.onComplete()
}
.subscribe(
{ s -> println(s) },
{ e -> println(e) },
{ println("onComplete") }
)
// ...and here's an example using RxKotlin. The named arguments help
// to give your code a little more clarity
Observable.create<String> { emitter ->
emitter.onNext("Hello, World!")
emitter.onComplete()
}
.subscribeBy(
onNext = { s -> println(s) },
onError = { e -> println(e) },
onComplete = { println("onComplete") }
)
}
i hope that helps!
Have a look at RxKotlin, that will simplify a lot of things and make code more concise.
val list = listOf("Alpha", "Beta", "Gamma", "Delta", "Epsilon")
list.toObservable() // extension function for Iterables
.filter { it.length >= 5 }
.subscribeBy( // named arguments for lambda Subscribers
onNext = { println(it) },
onError = { it.printStackTrace() },
onComplete = { println("Done!") }
)
val observer = object: Observer<Int> {
override fun onNext(t: Int) {
// Perform the value of `t`
}
override fun onComplete() {
// Perform something on complete
}
override fun onSubscribe(d: Disposable) {
// Disposable provided
}
override fun onError(e: Throwable) {
// Handling error
}
}