Algún desarrollador android? Kotlin

Pues últimamente me ha dado por intentar hacer alguna que otra app por diversión, pero también soy muy cabezón así que:

Tengo una app (de mierda por supuesto) funcional y tal, en test interno y le puse compras, cosa que me costó dios y ayuda y el tema es que no se confirman. No sé cómo implementar esa lógica y ya me está empezando a dar asco xD

He mirado en mil sitios, preguntado y demás pero no me aclaro por h o por b, aunque cabe decir que la mayoría está/parece desactualizado. De lo que dicen en la pag de android la verdad es que me resulta algo confuso. Y nada me preguntaba si algún eoliano que tenga el conocimiento y unos minutos pudiera echarme un cable.

Se supone que solo me faltaría un paso de todo el proceso, que es el de hacer la compra "acknowledged" para que google no haga refund o la cancele.

Podéis mandar mp directamente si os parece mejor y os enseño el código y demás. [beer]

PD: mis conocimientos son básicos, he aprendido viendo un par de tutoriales y trasteando en el IDE.

Saludoss
Tengo pendiente (voy con retraso ya...) actualizar la biblioteca de facturación de mis apps, uso una versión muy antigua y no tenía esto del acknowledge.

La última versión es la v4, que es la que deberías estar usando. Y, efectivamente, los tutoriales de las anteriores no te van a servir porque cambian cosas.

Como siempre, stack overflow al rescate XD

También hay bibliotecas de gente que hacen el trabajo sucio, implementar el API de compras y hacerlo bien contemplando todos los casos posibles y errores es algo farragoso, especialmente si vendes consumibles que se pueden comprar varias veces.

Para llevar cuatro versiones me sigue pareciendo un sistema complicado de usar, especialmente si lo único que quieres es vender el típico artículo no consumible para quitar anuncios o desbloquear todas las funciones.
@mocelet Hey, muchas gracias por responder antes que nada.

Efectivamente estoy usando la versión 4.1 del billing client. Y justo ese enlace que me has pasado es al que le estoy intentando sacar el jugo desde hace un par de días xD El rollo que eso es Java y lo tengo que pasar a Kotlin, que no es tan complicado pero ya me lío un poco. Y luego que yo no tengo la misma estructura que sale en ese post xD Si por ejemplo le pongo el UpdateListener en la class, al crear la función me sale con errores, ni idea de pq. Supongo que es por haber ido haciendo las cosas picoteando de distintos sitios. Es lo que pasa por querer correr antes de gatear imagino.

Imagino que tendré que empezar la activity de cero y copiar paso a paso pq si no acabo más liado que antes jajaj [beer] De todos modos cuando actualices si te acuerdas dame un toque, porqué llevo como una semana estancado en este punto y si no consigo solucionarlo entre hoy y mañana, la dejaré de lado para centrarme en otros proyectos hasta que aprenda xD pq la verdad que ya me desmotiva bastante.

Saludoss
@KYKUR88 De hecho, creo que ese hilo de SO es para la v3 XD Pensaba que el acknowledge lo metieron en la v4 pero también estaba en la v3, así que cuidado con copiar código viejo.

En la documentación oficial parece que lo explican paso a paso y tiene bastantes ejemplos de código de cada paso:

https://developer.android.com/google/pl ... /integrate

En el ejemplo que dan hace el acknowledge en el acto cuando recibe el evento de compra realizada.
@mocelet Sí, la documentación oficial es lo primero que seguí, aunque como digo hay cosas que me resultan súper confusas. Por ejemplo en la parte que te explica lo de confirmar compra dice:

"val client: BillingClient = ...
val acknowledgePurchaseResponseListener: AcknowledgePurchaseResponseListener = ..."

Y yo me quedo en plan, vale crucks y cómo sé yo como rellenar los puntos suspensivos si en los pasos anteriores no pone nada xD Debo ser muy tonto, no lo niego. Pero desde lo que pone ahí he probado varias cosas y nada, no lo sé poner bien + la falta de info por la red.. ratataaaa Y luego te pone ahí la suspend fun handlePurchase() pero si por ejemplo tengo cada compra anidada dentro de un botón distinto, la tengo q llamar dentro de cada botón o como? XD

Siento hacerte perder el tiempo, de hecho ya me doy con un canto en los dientes con que te hayas tomado la molestia de contestarme. Luego si puedo te paso una foto del código y me dices si me compensa más empezar de cero xDDD

Saludoss y mil gracias
KYKUR88 escribió:"val client: BillingClient = ...
val acknowledgePurchaseResponseListener: AcknowledgePurchaseResponseListener = ..."


Del client no dice nada porque se supone que lo has inicializado antes, es el mismo cliente del principio de la documentación.

Del listener tampoco dicen nada porque es eso, un listener para avisarte del resultado de la operación y lo implementas como quieras. Ahí o haces que tu clase implemente la interfaz y haga override de la función o creas un objeto directamente que la implemente. Técnicamente habría que comprobar que el resultado es correcto pero salvo que "se caiga Google" es difícil pensar que el ack no se va a procesar cuando acaba de confirmar la compra, así que mucha gente ni lo implementa y simplemente crea un listener vacío para que compile.
@mocelet Vale ^^' algo cortito sí que soy. Me dejas más tranquilo jajaj
mira:

https://ibb.co/CP7sZ15 https://ibb.co/qrvYq51


Si te preguntas pq coño tengo el binding y el findviewbyId es pq con el binding al poner las compras no me pillaba el botón xD y ya lo dejé así.

La segunda img es uno de los 4 botones con compra, en este caso es una SUBS y los otros 3 son idénticos pero tienen INAPP en lugar de sub.

Dónde debería implementar el ack exactamente? Después del .responseCode? Después de onBillingServiceDisconnected() ? fuera del botón? Uno para cada botón? [+risas]

Ya con esto no te mareo más compañero, gracias por tu tiempo.

Saludoss
@KYKUR88 Empiezo a entender por qué dices que el código de ejemplo cuesta seguirlo, es como esas películas que te las cuentan desordenadas [+risas] Además, en algunos ejemplos de Kotlin hay erratas (como comerse el parámetro purchase de handlePurchase, que en el ejemplo de Java está bien).

El caso, a grandes rasgos la historia es crear el billing client, iniciar el flujo de compra con el SKU que sea según al botón que le hayas dado y, en el PurchasesUpdatedListener, ver que la compra se ha completado llamando al handlePurchase y ejecutando el acknowledge si la compra fue satisfactoria.

El listener ahora lo tienes vacío, ahí iría el código del ejemplo del onPurchasesUpdated que recorre la lista de compras (Purchase) y llama al handlePurchase de cada una. En tu caso lo has puesto como lambda, así que no tienes que declarar la función. Y dentro del handlePurchase (recuerda añadirle el parámetro purchase: Purchase) el código del ejemplo ya llama al ack. El listener del ack si lo pones como lambda tampoco tienes que declararlo como val, así que esa línea que tenía puntos suspensivos te sobra.

Otro apunte, no copies y pegues el mismo código para el listener de cada botón porque eso queda muy feo. Al fin y al cabo la única diferencia en el comportamiento es el SKU. Hazte una función a la que pases por parámetro el SKU y así queda más limpio.
Gracias de nuevo, te contesto en cursiva:

mocelet escribió:El caso, a grandes rasgos la historia es crear el billing client, iniciar el flujo de compra con el SKU que sea según al botón que le hayas dado y, en el PurchasesUpdatedListener, ver que la compra se ha completado llamando al handlePurchase y ejecutando el acknowledge si la compra fue satisfactoria.

Sí, eso diría que lo tengo ya claro, gracias

El listener ahora lo tienes vacío, ahí iría el código del ejemplo del onPurchasesUpdated que recorre la lista de compras (Purchase) y llama al handlePurchase de cada una. En tu caso lo has puesto como lambda, así que no tienes que declarar la función. Y dentro del handlePurchase (recuerda añadirle el parámetro purchase: Purchase) el código del ejemplo ya llama al ack. El listener del ack si lo pones como lambda tampoco tienes que declararlo como val, así que esa línea que tenía puntos suspensivos te sobra.

Genial! no había caído en eso, y al menos ya entiendo q al estar en lambda no tengo q declarar la función. Si le añado el parámetro : Purchase, androidstudio me lo marca como muchos parámetros y la fun se deshabilita, en cambio si solo dejas (purchase) parece no dar fallo.

Luego en el listener del ack lo que he hecho ha sido añadirle { billingResult -> } pq si le pongo además purchases peta. La fun handlePurchase() me marca cada "purchase" en rojo, o sea que no sé de dónde la saco o si la tengo con otro nombre o q xD aparte el withContext tb me sale con error y el client, aunque ponga billingClient que se supone que es como lo he llamado desde un principio tampoco tira, se queda lleno de errores



Otro apunte, no copies y pegues el mismo código para el listener de cada botón porque eso queda muy feo. Al fin y al cabo la única diferencia en el comportamiento es el SKU. Hazte una función a la que pases por parámetro el SKU y así queda más limpio.

Muchas gracias por el apunte, era lo siguiente que iba a hacer una vez solucionara esto, aprender a reducir todo ese código pq como bien dices es lo mismo para distintos SKUs. Aparte de pulir la app que da asco xD



Como veo que me falta demasiado de lo básico, pq con lo que me has dicho me tendría que dar de sobra para implementarlo y ni con esas logro entender que es lo que falla haga lo que haga, lo aparcaré aquí y ya volveré más adelante. Me pondré con otros proyectos pq aquí ya me estoy amargando (como digo llevo muchos días con esto y lo poco que he avanzado ha sido para toparme algo peor xD y ya empiezo a frustrarme) e intentaré aprender más y mejor.

Dicho esto te agradezco enormemente tus aportes y aunque que parezca que no, he aprendido mucho más leyendo tus indicaciones que en un par de semanas buscando. Así que millones de gracias compañero, pero ya paso de seguir abusando de tu buena voluntad [beer]

Saludoss
@KYKUR88 "La fun handlePurchase() me marca cada "purchase" en rojo"

A eso me refería, que tienen una errata en el código de ejemplo de Kotlin, lo correcto sería fun handlePurchase(purchase: Purchase), porque le tienes que pasar la purchase que vas a procesar como parámetro.

En otras versiones mucho más antiguas daban incluso una app de ejemplo que venía muy bien porque era el código completo. Aquí no la he visto y el código de la documentación es como un puzzle que luego tienes que recomponer.
@mocelet Dios eres el amo xD

Hay que poner (purchase: Purchase) cuando la declaras "private fun handle...." y dejar solo (purchase) cuando la llamas [facepalm] JAJAJAJA acabo de solucionar gran parte del marrón!! Puede parecer obvio pero queda claro que mis conocimientos son casi inexistentes [angelito]

He hecho lo siguiente xD (este post va a quedar como historia ya):

EDIT: vale que el handlePurchase es suspend y no va fuera del onCreate xD

dentro de ackPurchaseResult he definido:
val client = BillingClient.newbuilder(context:this@GoproActivity).build()

porqué si no me dice que unresolved reference

el único problema que veo ahora es la handlePurchase(purchase) dentro del listener:

val purchasesUpdatedListener = PurchasesUpdatedListener { billingResult, purchases ->

if (billingResult.responseCode == BillingClient.BillingResponseCode.OK && purchases != null) {
for (purchase in purchases) {
handlePurchase(purchase)
}
} else if (billingResult.responseCode == BillingClient.BillingResponseCode.USER_CANCELED) {
// Handle an error caused by a user cancelling the purchase flow.
}
}


VAMOS HOSTIA que estaba a punto de tirar la toalla y tengo un subidón ahora de dopamina que pa qué xDDD



Saludoss
@KYKUR88 Definitivamente los ejemplos de Kotlin oficiales tienen erratas y omisiones, creo que es mejor encontrar un ejemplo parecido, tiene que haber tutoriales sencillos en Kotlin con la última versión de la biblioteca, una actividad y un par de botones para hacer compras.

En el ejemplo que dan en Kotlin, el handlePurchase es suspend porque usan corrutinas para todo y está pensado para llamarlas desde un ViewModel, pero el código está incompleto porque llaman la función a las bravas y eso no se puede (las funciones suspend bloquean el hilo de ejecución y hace falta llamarlas de forma especial especificando un "scope" de ejecución).

En la versión de Java que es la que estoy usando tienen los métodos que acaban en async como querySkuDetailsAsync en vez de querySkuDetails y es bastante más sencillo si nunca has usado corrutinas. De hecho, en el texto de la documentación solo hablan del async, es como si estuviera todo pensado para Java y luego hayan metido los ejemplos de Kotlin corriendo que no se corresponden con el texto. Normal que la gente se líe.

Casi te recomiendo no seguir la documentación oficial y encontrar algún blog que tenga un tutorial completo de app sencilla, ver la estructura y cómo organizan el código. Google sí tiene una app de ejemplo, pero ni la enlazo porque la estructura es bastante complicada y el código no te vale (usa DataSource y ViewModel y tú no necesitas más que una Activity sencilla).
@mocelet

Muchas gracias de nuevo, ya te digo que me parecía todo súper confuso (empecé por ahí) y luego fui buscando otros recursos pq no entendía nada y era solo errores y más errores, encima sin comprender muy bien el porqué.

Si te soy sincero el code spaghetti que tengo por ahí es a causa de eso, de ir probando cosas de un lado y de otro y algunas petar al probar otras cosas y al final pues me quedó esa birria xD Todo esto de hecho (las IAP) las conseguí meter viendo un vídeo en hindi [facepalm] hay muy poca por no decir CERO info al respecto. Pq el inglés no es problema pero el hindi wtf..

Vi cosas parecidas como por ejemplo: app ejemplo

Pero también me daba problemas el Security.Java, la fun verifyPurchase no había manera de importarla (aparte de algún q otro error).

o esto: app ejemplo 2 pero al ser Java si te soy sincero se me hace muy cuesta arriba traducir todo, pq hay cosas q aún no entiendo bien. xD

Lo que es Billing V4 en Kotlin yo no he sabido encontrar demasiado.

No sé si tú sabes algún sitio dónde se vea claro al menos y a partir de ahí yo me apaño. En teoría si lo explican bien soy capaz de reproducir lo que voy aprendiendo pero si no me entra, solo estoy haciendo copypaste de cosas que no sé ni lo que hacen.

Gracias y saludoss [beer]

PD: Cuando me forre con las apps me acordaré de ti ntp [qmparto] [qmparto]
Después de unas horas confirmamos que sigue siendo un pequeño infierno integrar la biblioteca bien. Al menos ahora se puede depurar con una cuenta de probador de licencias y la versión debug de la app, que antes había que firmarla como si fuera la app de producción y era horrible [+risas]

Hay empresas que se dedican a ofrecer bibliotecas y servicios de compras que, en teoría, te hacen la vida más fácil, una de ellas es RevenueCat (fundada por españoles, creo). También te ofrecen servidor para validar las firmas de la Play Store y ponérselo más difícil a quien quiera saltarse el pago. Quizá te interese, no sé cómo será su documentación y hasta qué punto es más fácil de integrar.

Voy a seguir optando por la biblioteca oficial (síndrome de Estocolmo a este paso [+risas]). Al final el único sitio donde "lo hacen bien" es en la app de ejemplo oficial TrivialDrive. Tienen versión en Java y Kotlin, yo estoy picoteando de ahí el código Java que me interesa para las in-app purchases porque no tengo suscripciones ni consumibles. Pero, como decía antes, mezclan demasiadas cosas, es un código muy denso y no es nada fácil seguirlo sin experiencia.

Gracias al ejemplo he aprendido que el onPurchasesUpdated solo se llama cuando lanzas el flujo de compra. Si quieres restaurar o consultar las compras automáticamente recomiendan llamar al queryPurchasesAsync en el onResume, un detallito sin importancia que no mencionan en la documentación. También hace falta obtener la información de los artículos antes de iniciar el proceso de compra, así que el botón de comprar debería estar desactivado hasta que se tenga el precio del artículo.

En los comentarios de la app mencionan que es extremadamente raro que el servicio pierda la conexión (es una conexión local con la Play Store), de ahí que en muchos ejemplos por Internet la gente ni implemente la reconexión (que yo la he fusilado del TrivialDrive).
@mocelet Perdona compi al no mencionarme no vi que habías comentado.

Al final me las apañé (con sangre sudor y lágrimas) para integrar todo bien con la oficial. "No es para tanto" ahora que veo el código finiquitado, pero solo Dios sabe lo que he sufrido...

Yo para no complicarme dejé también solo compras de productos únicos, si te interesa verlo me lo dices y te mando foto o algo (eso sí, Kotlin) [beer]

Lo de que no mencionen lo del queryPurchasesAsync en el onResume() es para darles de comer aparte... Investigaré más sobre eso pq al final lo que me interesa es que las compras sean consumibles, pero por ahora lanzaré la v1 así y ya para las siguientes actus iré puliendo más y añadiendo más cosillas. Tengo mono de tener una app subida xD

Lo que comentas del botón, q debería estar desactivado hasta que se tenga el precio, la verdad que no entiendo cómo será que lo hace, yo lanzo el proceso "entero" hasta el purchaseFlow dentro de cada botón, y cada botón carga una compra distinta. No he tenido problemas hasta ahora con las pruebas.

Le echaré un ojo al TrivialDrive que me comentas para implementarlo yo también por si aca. Muchas gracias!

Saludoss
KYKUR88 escribió:Lo que comentas del botón, q debería estar desactivado hasta que se tenga el precio, la verdad que no entiendo cómo será que lo hace, yo lanzo el proceso "entero" hasta el purchaseFlow dentro de cada botón, y cada botón carga una compra distinta. No he tenido problemas hasta ahora con las pruebas.


Ah, ya, depende de dónde llames al querySkuDetailsAsync, si dentro del botón o fuera. Es mejor llamarla fuera porque hay que llamarla de todas formas y esperar al resultado (que son objetos SkuDetails con el precio de cada artículo, si está en oferta y cosas así) para poder lanzar la ventana de pago con launchBillingFlow.

La ventaja de llamarlo fuera antes de apretar un botón es que lo llamas una vez y te devuelve la información de todos los artículos del tirón y así puedes poner los precios finales en los botones (a mí que una app me plante la ventana de pagar sin saber el precio no me gusta mucho).

Además, en principio el botón de comprar no debería estar disponible para no-consumibles si el usuario ya ha comprado el artículo en otro dispositivo o desinstaló la app y la volvió a instalar. Ahí entra lo de esperar a la respuesta del queryPurchasesAsync.

La otra opción que funciona y la usan en algunos tutoriales que ponías arriba es tener el botón habilitado, que el usuario le dé al comprar aunque ya lo haya comprado antes y le salga el mensaje de artículo ya comprado (BillingResponseCode.ITEM_ALREADY_OWNED). Ahí ya sabes que el usuario lo había comprado previamente, pero desde el punto de vista de experiencia de uso es mejorable porque obligas al usuario a apretar un botón que no era necesario [sonrisa]. Si hay muchos artículos no va a saber cuáles compró y cuáles no y va a tener que andar dándole a todos los botones.
15 respuestas