I am building an Android app in Flutter and Fortify code scan reported the warning: The application does not use the Google Play Service Updated Security Provider which may leave it vulnerable to future vulnerabilities in OpenSSL Library.
For a native Android app, I could follow the guide here but how do I fix the warning in a Flutter app?
In your MainActivity, use the code from this snippet by Google
private const val ERROR_DIALOG_REQUEST_CODE = 1
/**
* Sample activity using {#link ProviderInstaller}.
*/
class MainActivity : Activity(), ProviderInstaller.ProviderInstallListener {
private var retryProviderInstall: Boolean = false
//Update the security provider when the activity is created.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
ProviderInstaller.installIfNeededAsync(this, this)
}
/**
* This method is only called if the provider is successfully updated
* (or is already up-to-date).
*/
override fun onProviderInstalled() {
// Provider is up-to-date, app can make secure network calls.
}
/**
* This method is called if updating fails; the error code indicates
* whether the error is recoverable.
*/
override fun onProviderInstallFailed(errorCode: Int, recoveryIntent: Intent) {
GoogleApiAvailability.getInstance().apply {
if (isUserResolvableError(errorCode)) {
// Recoverable error. Show a dialog prompting the user to
// install/update/enable Google Play services.
showErrorDialogFragment(this#MainActivity, errorCode, ERROR_DIALOG_REQUEST_CODE) {
// The user chose not to take the recovery action
onProviderInstallerNotAvailable()
}
} else {
onProviderInstallerNotAvailable()
}
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int,
data: Intent) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == ERROR_DIALOG_REQUEST_CODE) {
// Adding a fragment via GoogleApiAvailability.showErrorDialogFragment
// before the instance state is restored throws an error. So instead,
// set a flag here, which will cause the fragment to delay until
// onPostResume.
retryProviderInstall = true
}
}
/**
* On resume, check to see if we flagged that we need to reinstall the
* provider.
*/
override fun onPostResume() {
super.onPostResume()
if (retryProviderInstall) {
// We can now safely retry installation.
ProviderInstaller.installIfNeededAsync(this, this)
}
retryProviderInstall = false
}
private fun onProviderInstallerNotAvailable() {
// This is reached if the provider cannot be updated for some reason.
// App should consider all HTTP communication to be vulnerable, and take
// appropriate action.
}
}
Link to android documentation
Related
I am trying to integrate stripe terminal code with my android app build using kotlin, unfortunately I am getting the following run time error which I could not able to fix
java.lang.IllegalStateException: initTerminal must be called before attempting to get the instance
The code I have added is used below
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_pay_screen)
onDiscoverReaders()
}
fun onDiscoverReaders() {
val config = DiscoveryConfiguration(
timeout = 0,
discoveryMethod = DiscoveryMethod.LOCAL_MOBILE,
isSimulated = false,
location = "xxxxxxxxxxxxxxx"
)
// Save this cancelable to an instance variable
discoveryCancelable = Terminal.getInstance().discoverReaders(config,
discoveryListener = object : DiscoveryListener {
override fun onUpdateDiscoveredReaders(readers: List<Reader>) {
}
}
, object : Callback {
override fun onSuccess() {
println("Finished discovering readers")
}
override fun onFailure(e: TerminalException) {
e.printStackTrace()
}
})
}
I have added this to one of my activity and my intention is to check if my phone is supporting stripe tap on mobile
I guess the issue could be calling onDiscoverReaders() from a wrong place, someone please help me to fix this issue
Thanks in advance
In stripe docs you can check
// Create your listener object. Override any methods that you want to be notified about
val listener = object : TerminalListener {
}
// Choose the level of messages that should be logged to your console
val logLevel = LogLevel.VERBOSE
// Create your token provider.
val tokenProvider = TokenProvider()
// Pass in the current application context, your desired logging level, your token provider, and the listener you created
if (!Terminal.isInitialized()) {
Terminal.initTerminal(applicationContext, logLevel, tokenProvider, listener)
}
// Since the Terminal is a singleton, you can call getInstance whenever you need it
Terminal.getInstance()
might be you missed to initialise terminal before getting Instance so try add above code before onDiscoverReaders()
The error speaks for itself - first you need to initialize the api terminal, and then call the terminal instance.
Based on the documentation, we follow the following steps to get started with the api terminal:
Initialize the terminal application in the application class of the
application
class App : Application() {
override fun onCreate() {
super.onCreate()
TerminalApplicationDelegate.onCreate(this)
}
}
We request the necessary permissions for correct work with the
terminal search (bluetooth, geolocation), if everything is provided,
we call the init terminal with parameters like that:
Terminal.initTerminal(
context = context,
logLevel = LogLevel.VERBOSE,
tokenProvider = TokenProvider(),
listener = object : TerminalListener {
override fun onUnexpectedReaderDisconnect(reader: Reader) {
Log.d("log", "onUnexpectedReaderDisconnect")
}
override fun onConnectionStatusChange(status: ConnectionStatus) {
super.onConnectionStatusChange(status)
Log.d("log", "onConnectionStatusChange")
}
override fun onPaymentStatusChange(status: PaymentStatus) {
super.onPaymentStatusChange(status)
Log.d("log", "onPaymentStatusChange")
}
}
)
After this initialization, you can call the terminal instance and
work with it.
I can't find resources on the internet on How to achieve the google API integration into an Compose based app.
I need help, especially in the AutoResolveHelper.resolveTask, how to do it in compose.
Thank you for your answers.
(That's mind blowing that there is no more documentation on this API, it's pretty difficult to implement).
Edit :
This is the traditional way to do it ->
private fun requestPayment() {
// Disables the button to prevent multiple clicks.
googlePayButton.isClickable = false
// The price provided to the API should include taxes and shipping.
// This price is not displayed to the user.
val garmentPrice = selectedGarment.getDouble("price")
val priceCents = Math.round(garmentPrice * PaymentsUtil.CENTS.toLong()) + SHIPPING_COST_CENTS
val paymentDataRequestJson = PaymentsUtil.getPaymentDataRequest(priceCents)
if (paymentDataRequestJson == null) {
Log.e("RequestPayment", "Can't fetch payment data request")
return
}
val request = PaymentDataRequest.fromJson(paymentDataRequestJson.toString())
// Since loadPaymentData may show the UI asking the user to select a payment method, we use
// AutoResolveHelper to wait for the user interacting with it. Once completed,
// onActivityResult will be called with the result.
if (request != null) {
AutoResolveHelper.resolveTask(
paymentsClient.loadPaymentData(request), this, LOAD_PAYMENT_DATA_REQUEST_CODE)
}
}
/**
* Handle a resolved activity from the Google Pay payment sheet.
*
* #param requestCode Request code originally supplied to AutoResolveHelper in requestPayment().
* #param resultCode Result code returned by the Google Pay API.
* #param data Intent from the Google Pay API containing payment or error data.
* #see [Getting a result
* from an Activity](https://developer.android.com/training/basics/intents/result)
*/
public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
when (requestCode) {
// Value passed in AutoResolveHelper
LOAD_PAYMENT_DATA_REQUEST_CODE -> {
when (resultCode) {
RESULT_OK ->
data?.let { intent ->
PaymentData.getFromIntent(intent)?.let(::handlePaymentSuccess)
}
RESULT_CANCELED -> {
// The user cancelled the payment attempt
}
AutoResolveHelper.RESULT_ERROR -> {
AutoResolveHelper.getStatusFromIntent(data)?.let {
handleError(it.statusCode)
}
}
}
// Re-enables the Google Pay payment button.
googlePayButton.isClickable = true
}
}
}
I recently came into this exact same issue. I didn't want to add the code to the activity and make an ugly, unreadable code so I ended up doing this:
In your composable, add this code to the click modifier or whatever:
val task = paymentsClient.loadPaymentData(request)
task.addOnCompleteListener { completedTask ->
if (completedTask.isSuccessful) {
completedTask.result.let{
//Do whatever you want
}
} else {
when (val exception = completedTask.exception) {
is ResolvableApiException -> {
resolvePaymentForResult.launch(
IntentSenderRequest.Builder(exception.resolution).build()
)
}
is ApiException -> {
Log.e("Google Pay API error", "Error code: ${exception.statusCode}, Message: ${exception.message}")
}
else -> {
Log.e("Unexpected non API exception")
}
}
}
}
With resolvePaymentForResult being:
val resolvePaymentForResult = rememberLauncherForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) {
result: ActivityResult ->
when (result.resultCode) {
RESULT_OK ->
result.data?.let { intent ->
PaymentData.getFromIntent(intent)?.let{
//Do whatever you want
}
}
RESULT_CANCELED -> {
// The user cancelled the payment attempt
}
}
}
You can always move paymentsClient.loadPaymentData(request) to your ViewModel if that's your architecture too!
Hope that will clean up your code a little bit more :)
I am using Google Fit API to get fitness data for a kotlin app.
But API does not work in internal testing on Google Play Developer Console.
When connecting the USB cable and install the APK directly, it will succeed, so I think that the Google API setting(Sha1 Finger print,etc) is correct.
For the part that links with the API, the official github code is used as it is.
Do you have any information about my error?
/**
* This enum is used to define actions that can be performed after a successful sign in to Fit.
* One of these values is passed to the Fit sign-in, and returned in a successful callback, allowing
* subsequent execution of the desired action.
*/
enum class FitActionRequestCode {
SUBSCRIBE,
READ_DATA
}
/**
* This sample demonstrates combining the Recording API and History API of the Google Fit platform
* to record steps, and display the daily current step count. It also demonstrates how to
* authenticate a user with Google Play Services.
*/
class MainActivity : AppCompatActivity() {
private val fitnessOptions = FitnessOptions.builder()
.addDataType(DataType.TYPE_STEP_COUNT_CUMULATIVE)
.addDataType(DataType.TYPE_STEP_COUNT_DELTA)
.build()
private val runningQOrLater =
android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// This method sets up our custom logger, which will print all log messages to the device
// screen, as well as to adb logcat.
initializeLogging()
checkPermissionsAndRun(FitActionRequestCode.SUBSCRIBE)
}
private fun checkPermissionsAndRun(fitActionRequestCode: FitActionRequestCode) {
if (permissionApproved()) {
fitSignIn(fitActionRequestCode)
} else {
requestRuntimePermissions(fitActionRequestCode)
}
}
/**
* Checks that the user is signed in, and if so, executes the specified function. If the user is
* not signed in, initiates the sign in flow, specifying the post-sign in function to execute.
*
* #param requestCode The request code corresponding to the action to perform after sign in.
*/
private fun fitSignIn(requestCode: FitActionRequestCode) {
if (oAuthPermissionsApproved()) {
performActionForRequestCode(requestCode)
} else {
requestCode.let {
GoogleSignIn.requestPermissions(
this,
requestCode.ordinal,
getGoogleAccount(), fitnessOptions
)
}
}
}
/**
* Runs the desired method, based on the specified request code. The request code is typically
* passed to the Fit sign-in flow, and returned with the success callback. This allows the
* caller to specify which method, post-sign-in, should be called.
*
* #param requestCode The code corresponding to the action to perform.
*/
private fun performActionForRequestCode(requestCode: FitActionRequestCode) = when (requestCode) {
FitActionRequestCode.READ_DATA -> readData()
FitActionRequestCode.SUBSCRIBE -> subscribe()
}
var gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestEmail()
.build()
private fun oAuthPermissionsApproved() = GoogleSignIn.hasPermissions(
getGoogleAccount(),
fitnessOptions
)
/**
* Gets a Google account for use in creating the Fitness client. This is achieved by either
* using the last signed-in account, or if necessary, prompting the user to sign in.
* `getAccountForExtension` is recommended over `getLastSignedInAccount` as the latter can
* return `null` if there has been no sign in before.
*/
private fun getGoogleAccount() = GoogleSignIn.getAccountForExtension(this, fitnessOptions)
/**
* Handles the callback from the OAuth sign in flow, executing the post sign in function
*/
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (resultCode) {
RESULT_OK -> {
val postSignInAction = FitActionRequestCode.values()[requestCode]
postSignInAction.let {
performActionForRequestCode(postSignInAction)
}
}
else -> oAuthErrorMsg(requestCode, resultCode)
}
}
private fun oAuthErrorMsg(requestCode: Int, resultCode: Int) {
val message = """
There was an error signing into Fit. Check the troubleshooting section of the README
for potential issues.
Request code was: $requestCode
Result code was: $resultCode
""".trimIndent()
Log.e(TAG, message)
}
/** Records step data by requesting a subscription to background step data. */
private fun subscribe() {
// To create a subscription, invoke the Recording API. As soon as the subscription is
// active, fitness data will start recording.
Fitness.getRecordingClient(this, getGoogleAccount())
.subscribe(DataType.TYPE_STEP_COUNT_CUMULATIVE)
.addOnCompleteListener { task ->
if (task.isSuccessful) {
Log.i(TAG, "Successfully subscribed!")
} else {
Log.w(TAG, "There was a problem subscribing.", task.exception)
}
}
}
/**
* Reads the current daily step total, computed from midnight of the current day on the device's
* current timezone.
*/
private fun readData() {
Fitness.getHistoryClient(this, getGoogleAccount())
.readDailyTotal(DataType.TYPE_STEP_COUNT_DELTA)
.addOnSuccessListener { dataSet ->
val total = when {
dataSet.isEmpty -> 0
else -> dataSet.dataPoints.first().getValue(Field.FIELD_STEPS).asInt()
}
Log.i(TAG, "Total steps: $total")
}
.addOnFailureListener { e ->
Log.w(TAG, "There was a problem getting the step count.", e)
}
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
// Inflate the main; this adds items to the action bar if it is present.
menuInflater.inflate(R.menu.main, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
val id = item.itemId
if (id == R.id.action_read_data) {
fitSignIn(FitActionRequestCode.READ_DATA)
return true
}
return super.onOptionsItemSelected(item)
}
/** Initializes a custom log class that outputs both to in-app targets and logcat. */
private fun initializeLogging() {
// Wraps Android's native log framework.
val logWrapper = LogWrapper()
// Using Log, front-end to the logging chain, emulates android.util.log method signatures.
Log.setLogNode(logWrapper)
// Filter strips out everything except the message text.
val msgFilter = MessageOnlyLogFilter()
logWrapper.next = msgFilter
// On screen logging via a customized TextView.
val logView = findViewById<View>(R.id.sample_logview) as LogView
TextViewCompat.setTextAppearance(logView, R.style.Log)
logView.setBackgroundColor(Color.WHITE)
msgFilter.next = logView
Log.i(TAG, "Ready")
}
private fun permissionApproved(): Boolean {
val approved = if (runningQOrLater) {
PackageManager.PERMISSION_GRANTED == ActivityCompat.checkSelfPermission(
this,
Manifest.permission.ACTIVITY_RECOGNITION
)
} else {
true
}
return approved
}
private fun requestRuntimePermissions(requestCode: FitActionRequestCode) {
val shouldProvideRationale =
ActivityCompat.shouldShowRequestPermissionRationale(
this,
Manifest.permission.ACTIVITY_RECOGNITION
)
// Provide an additional rationale to the user. This would happen if the user denied the
// request previously, but didn't check the "Don't ask again" checkbox.
requestCode.let {
if (shouldProvideRationale) {
Log.i(TAG, "Displaying permission rationale to provide additional context.")
Snackbar.make(
findViewById(R.id.main_activity_view),
R.string.permission_rationale,
Snackbar.LENGTH_INDEFINITE
)
.setAction(R.string.ok) {
// Request permission
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.ACTIVITY_RECOGNITION),
requestCode.ordinal
)
}
.show()
} else {
Log.i(TAG, "Requesting permission")
// Request permission. It's possible this can be auto answered if device policy
// sets the permission in a given state or the user denied the permission
// previously and checked "Never ask again".
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.ACTIVITY_RECOGNITION),
requestCode.ordinal
)
}
}
}
override fun onRequestPermissionsResult(
requestCode: Int, permissions: Array<String>,
grantResults: IntArray
) {
when {
grantResults.isEmpty() -> {
// If user interaction was interrupted, the permission request
// is cancelled and you receive empty arrays.
Log.i(TAG, "User interaction was cancelled.")
}
grantResults[0] == PackageManager.PERMISSION_GRANTED -> {
// Permission was granted.
val fitActionRequestCode = FitActionRequestCode.values()[requestCode]
fitActionRequestCode.let {
fitSignIn(fitActionRequestCode)
}
}
else -> {
// Permission denied.
// In this Activity we've chosen to notify the user that they
// have rejected a core permission for the app since it makes the Activity useless.
// We're communicating this message in a Snackbar since this is a sample app, but
// core permissions would typically be best requested during a welcome-screen flow.
// Additionally, it is important to remember that a permission might have been
// rejected without asking the user for permission (device policy or "Never ask
// again" prompts). Therefore, a user interface affordance is typically implemented
// when permissions are denied. Otherwise, your app could appear unresponsive to
// touches or interactions which have required permissions.
}
}
}
}```
[1]: https://i.stack.imgur.com/UpQLO.png
We had the exact same issue and it doesn't seem to be documented anywhere. Even tho this question is over 1-year old I think it's nice to have it answered.
When you upload an APK for internal testing, it's not signed with your certificate, it's signed using a certificate issued by Google. For this reason, the SHA1 fingerprint configured for the fitness API does not match.
To overcome the issue, what you have to do is go to the developer console> internal testing > version details > downloads and download the apk from there. Once you have the apk, obtain the sha-1 fingerprint directly from it using
keytool -printcert -jarfile fileName.apk
That'll print the details of the certificate, including the SHA-1 fingerprint. Configure that fingerprint for the fitness oauth certificate and it'll work!
Remember to change this to the correct fingerprint when going to beta/production
It's nice to provide a closeup for this
In my Android app I have to show a list of available services on the network published by another machine (RPi 3B con Raspbian Stretch) using avahi 0.6.32 (Bonjour/zeroconf daemon for Linux). I obtain the list on Android phone using NsdManager.
But, during testing, I'm getting a strange behavior: when I switch WiFi off and back on in the phone, most of the times the services are discovered, then immediately lost and then rediscovered (instead of just discovered once) and all of this in less than a second.
This causes the list of services to briefly appear on screen, then disappear and finally reappear almost immediately, but it's still very noticeable. And it forces the services to be discovered and resolved twice. As I expect to have lots of phones connecting to several services in the same LAN, I want to avoid overloading the network.
I'm not sure if I'm doing something wrong or it's just the way NsdManager works on Android. To reduce possible sources of problem, I commented out the lines that resolve the services (leaving only the log messages) but the problem persisted (more then half of the times).
How can I solve it?
Sample extract from Logcat:
2019-09-26 04:33:50.262 27300-27420/com.example.myapp D/NsdHelper$initializeDiscoveryListener: Service discovery success: name: MyService-1490247, type: _mytype._tcp., host: null, port: 0, txtRecord:
2019-09-26 04:33:50.879 27300-27420/com.example.myapp D/NsdHelper$initializeDiscoveryListener: Service lost: name: MyService-1490247, type: _mytype._tcp., host: null, port: 0, txtRecord:
2019-09-26 04:33:50.970 27300-27420/com.example.myapp D/NsdHelper$initializeDiscoveryListener: Service discovery success: name: MyService-1490247, type: _mytype._tcp., host: null, port: 0, txtRecord:
I'm testing on a Samsung Note 8 with Android O. I tried with 2 different WiFi routers and the behavior is the same.
I'm using the following NsdHelper class (in Kotlin):
import android.content.Context
import android.net.nsd.NsdManager
import android.net.nsd.NsdServiceInfo
import timber.log.Timber
import java.util.*
import java.util.concurrent.ConcurrentLinkedQueue
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.collections.ArrayList
abstract class NsdHelper(val context: Context) {
// Declare DNS-SD related variables for service discovery
val nsdManager: NsdManager? = context.getSystemService(Context.NSD_SERVICE) as NsdManager?
private var discoveryListener: NsdManager.DiscoveryListener? = null
private var resolveListener: NsdManager.ResolveListener? = null
private var resolveListenerBusy = AtomicBoolean(false)
private var pendingNsdServices = ConcurrentLinkedQueue<NsdServiceInfo>()
var resolvedNsdServices: MutableList<NsdServiceInfo> =
Collections.synchronizedList(ArrayList<NsdServiceInfo>())
companion object {
// Type of services to look for
const val NSD_SERVICE_TYPE: String = "_mytype._tcp."
// Services' Names must start with this
const val NSD_SERVICE_NAME: String = "MyService-"
}
// Initialize Listeners
fun initializeNsd() {
// Initialize only resolve listener
initializeResolveListener()
}
// Instantiate DNS-SD discovery listener
// used to discover available Sonata audio servers on the same network
private fun initializeDiscoveryListener() {
Timber.d("Initialize DiscoveryListener")
// Instantiate a new DiscoveryListener
discoveryListener = object : NsdManager.DiscoveryListener {
override fun onDiscoveryStarted(regType: String) {
// Called as soon as service discovery begins.
Timber.d("Service discovery started: $regType")
}
override fun onServiceFound(service: NsdServiceInfo) {
// A service was found! Do something with it
Timber.d("Service discovery success: $service")
if ( service.serviceType == NSD_SERVICE_TYPE &&
service.serviceName.startsWith(NSD_SERVICE_NAME) ) {
// Both service type and service name are the ones we want
// If the resolver is free, resolve the service to get all the details
if (resolveListenerBusy.compareAndSet(false, true)) {
nsdManager?.resolveService(service, resolveListener)
} else {
// Resolver was busy. Add the service to the list of pending services
pendingNsdServices.add(service)
}
} else {
// Not our service. Log message but do nothing else
Timber.d("Not our Service - Name: ${service.serviceName}, Type: ${service.serviceType}")
}
}
override fun onServiceLost(service: NsdServiceInfo) {
Timber.d("Service lost: $service")
// If the lost service was in the queue of pending services, remove it
synchronized(pendingNsdServices) {
val iterator = pendingNsdServices.iterator()
while (iterator.hasNext()) {
if (iterator.next().serviceName == service.serviceName) iterator.remove()
}
}
// If the lost service was in the list of resolved services, remove it
synchronized(resolvedNsdServices) {
val iterator = resolvedNsdServices.iterator()
while (iterator.hasNext()) {
if (iterator.next().serviceName == service.serviceName) iterator.remove()
}
}
// Do the rest of the processing for the lost service
onNsdServiceLost(service)
}
override fun onDiscoveryStopped(serviceType: String) {
Timber.d("Discovery stopped: $serviceType")
}
override fun onStartDiscoveryFailed(serviceType: String, errorCode: Int) {
Timber.e("Start Discovery failed: Error code: $errorCode")
stopDiscovery()
}
override fun onStopDiscoveryFailed(serviceType: String, errorCode: Int) {
Timber.e("Stop Discovery failed: Error code: $errorCode")
nsdManager?.stopServiceDiscovery(this)
}
}
}
// Instantiate DNS-SD resolve listener to get extra information about the service
private fun initializeResolveListener() {
Timber.d("Initialize ResolveListener")
resolveListener = object : NsdManager.ResolveListener {
override fun onServiceResolved(service: NsdServiceInfo) {
Timber.d("Service Resolve Succeeded: $service")
// Register the newly resolved service into our list of resolved services
resolvedNsdServices.add(service)
// Process the newly resolved service
onNsdServiceResolved(service)
// Process the next service waiting to be resolved
resolveNextInQueue()
}
override fun onResolveFailed(serviceInfo: NsdServiceInfo, errorCode: Int) {
// Called when the resolve fails. Use the error code to debug.
Timber.e("Resolve failed: $serviceInfo - Error code: $errorCode")
// Process the next service waiting to be resolved
resolveNextInQueue()
}
}
}
// Start discovering services on the network
fun discoverServices() {
// Cancel any existing discovery request
stopDiscovery()
initializeDiscoveryListener()
// Start looking for available audio channels in the network
nsdManager?.discoverServices(NSD_SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, discoveryListener)
}
// Stop DNS-SD service discovery
fun stopDiscovery() {
if (discoveryListener != null) {
Timber.d("stopDiscovery() called")
try {
nsdManager?.stopServiceDiscovery(discoveryListener)
} finally {
}
discoveryListener = null
}
}
// Resolve next NSD service pending resolution
private fun resolveNextInQueue() {
// Get the next NSD service waiting to be resolved from the queue
val nextNsdService = pendingNsdServices.poll()
if (nextNsdService != null) {
// There was one. Send to be resolved.
nsdManager?.resolveService(nextNsdService, resolveListener)
} else {
// There was no pending service. Release the flag
resolveListenerBusy.set(false)
}
}
// Function to be overriden with custom logic for new service resolved
abstract fun onNsdServiceResolved(service: NsdServiceInfo)
// Function to be overriden with custom logic for service lost
abstract fun onNsdServiceLost(service: NsdServiceInfo)
}
On the init block of the View Model, I start the service discovery:
class MyViewModel(application: Application) : AndroidViewModel(application) {
// Get application context
private val myAppContext: Context = getApplication<Application>().applicationContext
// Declare NsdHelper object for service discovery
private val nsdHelper: NsdHelper? = object : NsdHelper(myAppContext) {
override fun onNsdServiceResolved(service: NsdServiceInfo) {
// A new network service is available
// Update list of available services
updateServicesList()
}
override fun onNsdServiceLost(service: NsdServiceInfo) {
// A network service is no longer available
// Update list of available services
updateServicesList()
}
}
// Block that is run when the view model is created
init {
Timber.d("init block called")
// Initialize DNS-SD service discovery
nsdHelper?.initializeNsd()
// Start looking for available audio channels in the network
nsdHelper?.discoverServices()
}
// Called when the view model is destroyed
override fun onCleared() {
Timber.d("onCleared called")
nsdHelper?.stopDiscovery()
super.onCleared()
}
private fun updateServicesList() {
// Put the logic here to show the services on screen
return
}
}
Note: Timber is a logging utility, almost a direct replacement for standard Log commands, but easier to use.
I'm always getting incomplete at onCompletePayment and I'm also checked stripe sample app but it's also not working for me. I have check lot but I unable rectify the issue.
So what's would be error on my side?
Source :
PaymentConfiguration.init(BuildConfig.STRIPE_PUBLISHABLE_KEY)
/* Initialized customer*/
private fun setupPaymentSession() {
mPaymentSession = PaymentSession(mvpView?.baseActivity!!)
val paymentSessionInitialized = mPaymentSession!!.init(object : PaymentSession.PaymentSessionListener {
override fun onCommunicatingStateChanged(isCommunicating: Boolean) {
if (isCommunicating) {
} else {
}
}
override fun onError(errorCode: Int, errorMessage: String?) {
}
override fun onPaymentSessionDataChanged(data: PaymentSessionData) {
mPaymentSessionData = data
checkForCustomerUpdates()
}
}, PaymentSessionConfig.Builder()
/* .setPrepopulatedShippingInfo(getExampleShippingInfo())
.setHiddenShippingInfoFields(ShippingInfoWidget.PHONE_FIELD, ShippingInfoWidget.CITY_FIELD)*/
.setShippingInfoRequired(false)
.build())
if (paymentSessionInitialized) {
mPaymentSession?.setCartTotal(20L)
}
}
override fun handlePaymentData(requestCode: Int, resultCode: Int, data: Intent?) {
if (data != null) {
mPaymentSession?.handlePaymentData(requestCode, resultCode, data)
mPaymentSession?.completePayment(PaymentCompletionProvider { paymentData, listener ->
Toast.makeText(mvpView?.baseActivity!!, "success" + paymentData.paymentResult, Toast.LENGTH_SHORT).show()
listener.onPaymentResult(paymentData.paymentResult)
})
}
}
Not familiar with Kotlin but in the code snippet you provided, I'd suggest not to override handlePaymentData.
Instead call mPaymentSession.handlePaymentData in the onActivityResult of your host Activity like it is suggested in the doc here or as shown in the example here so that any updates to the PaymentSessionData is reported to your PaymentSessionListener that you attached when initializing PaymentSession (i.e with mPaymentSession!!.init).
Also generally, depending on your app Checkout flow, you would want to call mPaymentSession.completePayment(...) as a result of your user clicking for example on a "Pay" button.
You would pass to the completePayment(...) call a PaymentCompletionProvider which would:
send an HTTP request to your backend so that you can create a charge using Stripe's API
mark the result of the payment using listener.onPaymentResult(...) passing PaymentResultListener.SUCCESS in the case where the payment was for example successful.
I don't think that the example app has an example of this but in Java you could for example have a click listener on your "Pay" button setup like below:
Button payButton = findViewById(R.id.pay_bttn);
payButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (mPaymentSessionData.isPaymentReadyToCharge() && mPaymentSessionData.getPaymentResult() == PaymentResultListener.SUCCESS) {
// Use the data to complete your charge - see below.
mPaymentSession.completePayment(new PaymentCompletionProvider() {
#Override
public void completePayment(#NonNull PaymentSessionData data, #NonNull PaymentResultListener listener) {
Log.v(TAG, "Completing payment with data: " + data.toString());
// This is where you want to call your backend...
// In this case mark the payment as Successful
listener.onPaymentResult(PaymentResultListener.SUCCESS);
}
});
}
}
});
I hope this helps.