Making Chat App using Smack ,
Try to get the Last Seen time of user but getting following Exception
org.jivesoftware.smack.XMPPException$XMPPErrorException: XMPPError: subscription-required - auth
Code :
public void getLastSeen(String JID) {
LastActivityManager mLastActivity = LastActivityManager.getInstanceFor (connection);
try {
try {
mLastActivity.getLastActivity (JID);
Log.e (TAG, "" + mLastActivity.getLastActivity (JID));
} catch (SmackException.NoResponseException e) {
e.printStackTrace ( );
}
} catch (XMPPException.XMPPErrorException e) {
e.printStackTrace ( );
} catch (SmackException.NotConnectedException e) {
e.printStackTrace ( );
}
}
getting above Exception on following line
mLastActivity.getLastActivity (JID);
Anybody know why getting this exception ?
Likely because you need to be subscribed to the contact's presence in order to retrieve the last activity.
First you need to setup roster once xmpp connection established :
private fun setupRoaster() {
if (conn1 == null) {
Timber.d("setupRoaster failed , due to connection is null $conn1")
} else {
conn1?.let {
roster = Roster.getInstanceFor(conn1)
roster?.subscriptionMode = Roster.SubscriptionMode.manual
roster?.addRosterListener(this)
Timber.d("setupRoaster roster?.entryCount : ${roster?.entryCount}")
roster?.addSubscribeListener(object:SubscribeListener{
override fun processSubscribe(
from: Jid?,
subscribeRequest: Presence?
): SubscribeListener.SubscribeAnswer {
Timber.d("setupRoaster SubscribeListener calledback Approved")
return SubscribeListener.SubscribeAnswer.Approve
}
})
if(BuildConfig.DEBUG){
//Here we are observed all the roster contacts and status
roster?.let {
for(ros in it.entries){
Timber.d("setupRoaster Info isSubscriptionPending :: ${ros.isSubscriptionPending} isApproved :: ${ros.isApproved} type :: ${ros.type} isSubscribedToMyPresence : ${roster?.isSubscribedToMyPresence(ros.jid)} ros.jid : ${ros.jid}")
//ros.isSubscriptionPending
}
}
}
Timber.d("setupRoaster success")
}
Timber.d("setupRoaster failed $conn1")
}
}
To add any user into your roster use below code :
override suspend fun addContactToRoster(toUserId: String, name: String) {
Coroutines.io {
Timber.d("addContactToRoster 1 $toUserId")
if(roster == null){
setupRoaster()
}
if (roster != null) {
Timber.d("addContactToRoster 2 roster?.isLoaded : ${roster?.isLoaded} ")
try {
roster?.let {
if(it.isLoaded && !it.isSubscribedToMyPresence(getJabberId(toUserId))){
val presence = Presence(Presence.Type.subscribe)
presence.setTo(getJabberId(toUserId))
presence.setType(Presence.Type.subscribed)
conn1?.sendStanza(presence)
roster?.createEntry(getJabberId(toUserId), name, null)
}
}
Timber.d("addContactToRoster Contact added to roster successfully")
} catch (e: SmackException.NotLoggedInException) {
Timber.d("addContactToRoster SmackException.NotLoggedInException called ${e.message} conn1?.isConnected ${conn1?.isConnected} conn1.isAuthenticated : ${conn1?.isAuthenticated}")
login()
} catch (e: SmackException.NoResponseException) {
Timber.d("addContactToRoster SmackException.NoResponseException called ${e.message} conn1?.isConnected ${conn1?.isConnected} conn1.isAuthenticated : ${conn1?.isAuthenticated}")
} catch (e: SmackException.NotConnectedException) {
Timber.d("addContactToRoster SmackException.NotConnectedException called ${e.message} conn1?.isConnected ${conn1?.isConnected} conn1.isAuthenticated : ${conn1?.isAuthenticated}")
}
} else {
Timber.d("addContactToRoster Roster not initilized,")
Timber.d("addContactToRoster May when user comes first time at that time internet not available so connection not established")
}
}
}
Once user successfully added to roster and receiver accept your subscription than you can last get activity/last seen by using below method :
fun getLastActivity(userId: String): String? {
Timber.d("XMPP :: getLastActivity $userId called conn1 : $conn1")
val jabberId = getJabberId(userId)
jabberId?.let {
Timber.d("XMPP :: getLastActivity 1 ${jabberId}")
conn1?.let {
Timber.d("XMPP :: getLastActivity 2")
if (it.isConnected && it.isAuthenticated) {
Timber.d("XMPP :: getLastActivity 3")
try {
val lastActivityManager: LastActivityManager =
LastActivityManager.getInstanceFor(conn1)
//val jid : Jid = JidCreate.from("u1304#quantumpigeon.com");
val status = lastActivityManager.isLastActivitySupported(jabberId)
val lastStatus = lastActivityManager.getLastActivity(jabberId)
Timber.d(
"XMPP :: lastStatus.toString $lastStatus \n lastStatus.lastActivity ${lastStatus.lastActivity} " +
"\n lastStatus.idleTime : ${lastStatus.idleTime} \n lastStatus.message : ${lastStatus.message} \n lastStatus.statusMessage : ${lastStatus.statusMessage}"
)
val milliSeconds =
applicationContext.getTrueTimeNow().time - (lastStatus.lastActivity * 1000)
//val lastSeen = getDate(milliSeconds, "dd/MM/yyyy hh:mm:ss.SSS")
val lastSeen = getLastSeen(milliSeconds)
Timber.d("XMPP :: isLastActivitySupported : $status lastStatus : $lastStatus LastSeen : $lastSeen")
return lastSeen
} catch (e: XMPPException.XMPPErrorException) {
Timber.d("XMPP :: Error in get last activity : ${e.message}")
} catch (e: SmackException.NoResponseException) {
Timber.d("XMPP :: SmackException.NoResponseException. : ${e.message}")
} catch (e: SmackException.NotConnectedException) {
Timber.d("XMPP :: SmackException.NotConnectedException. : ${e.message}")
}
} else {
Timber.d("XMPP :: handleNotConnectedException : ${it.isConnected} or authenticated ${it.isAuthenticated}")
// handleNotConnectedException()
}
Timber.d("XMPP :: Connection not connected : ${it.isConnected} or authenticated ${it.isAuthenticated}")
}
Timber.d("XMPP :: Connection not established $conn1")
}
return null
}
Related
i am implementing SignalR on client side with android using kotlin. I can connect and can get connection id its mean connection established succeeded after that invoke and subscribed it, when i call hubConnection.on() it could not move inside any log or nor catch any exception. where i have done mistake any help is appreciated
lifecycle.coroutineScope.launchWhenCreated {
hubConnection =
HubConnectionBuilder.create("baseurl",)
.build()
hubConnection.start().blockingAwait()
Log.e("123**** ", "Established connection......" + hubConnection.connectionId)
val userID = "Model.user_id"
try {
hubConnection.invoke(
"Subscribe",
hubConnection.connectionId,
arrayListOf(
userID,
"$userID+_messaging",
"$userID+_notification",
"$userID+_setting",
"$userID+_plan",
"$userID+_refresh"
)
).subscribe()
} catch (e: Exception) {
Log.e("ExceptionDone: ", e.toString())
}
try {
hubConnection.on("ReceiveSimplifiedRealTime", { response: String ->
// runOnUiThread {
Log.e("Receive123**** ", response)
//}
}, String::class.java)
} catch (e: Exception) {
Log.e("Receive123**** ", e.toString())
} catch (e: ExecutionException) {
Log.e("Receive123**** ", e.toString())
}
}
I'm trying to test a validity function. My function is like this:
class InvalidCredentialException(message: String) : Exception(message)
#Throws
fun credentialValidityChecker(email: String, password: String, nameAndFamily: String? = null) {
when {
email.isBlank() -> {
throw InvalidCredentialException("Email address can't left blank.")
}
!Patterns.EMAIL_ADDRESS.matcher(email)
.matches() -> {
throw InvalidCredentialException("Email address format is not correct.")
}
password.isBlank() -> {
throw InvalidCredentialException("Password can't left blank.")
}
password.length < 5 -> {
throw InvalidCredentialException("Password should have at least 5 characters.")
}
nameAndFamily != null -> {
if (nameAndFamily.isBlank())
throw InvalidCredentialException("Name and family can't left blank.")
}
}
}
I use this function to throw in case of any problem with the user credential. Otherwise, nothing happens, and the code continues. The exception is handled in other application layers.
and here are my test cases:
class CredentialValidityTest {
#Test
fun emptyEmail_raiseEmptyEmailException() {
try {
credentialValidityChecker(email = "", password = "12345")
fail("Empty email should raise exception.")
} catch (e: InvalidCredentialException) {
assertThat(e.message).isEqualTo("Email address can't left blank.")
}
}
#Test
fun wrongFormatEmail_raiseWrongEmailException() {
val wrongFormatEmailList = listOf(
"test", "test#", "test#application",
"test#application.", "test#.", "test.application#com"
)
for (email in wrongFormatEmailList)
try {
credentialValidityChecker(email = email, password = "12345")
fail("Wrong format email should raise exception.")
} catch (e: InvalidCredentialException) {
assertThat(e.message).isEqualTo("Email address format is not correct.")
}
}
#Test
fun emptyPassword_raiseEmptyPasswordException() {
try {
credentialValidityChecker(email = "test#application.com", password = "")
fail("Empty password should raise exception.")
} catch (e: InvalidCredentialException) {
assertThat(e.message).isEqualTo("Password can't left blank.")
}
}
#Test
fun weakPassword_raiseWeakPasswordException() {
try {
credentialValidityChecker(email = "test#application.com", password = "1234")
fail("weak password should raise exception.")
} catch (e: InvalidCredentialException) {
assertThat(e.message).isEqualTo("Password should have at least 5 characters.")
}
}
#Test
fun emptyNameAndFamily_raiseEmptyNameAndFamilyException() {
try {
credentialValidityChecker(
email = "test#application.com",
password = "12345",
nameAndFamily = ""
)
fail("Empty name and family should raise exception.")
} catch (e: InvalidCredentialException) {
assertThat(e.message).isEqualTo("Name and family can't left blank.")
}
}
}
The problem is:
Only the first test case pass, which checks email not be blank. The other test cases fail with the java.lang.NullPointerException error.
What is the problem?
Try using PatternsCompat.EMAIL_ADDRESS instead of Patterns.EMAIL_ADDRESS
My app is checking for unread emails in the background, the problem is that i need to save and retrieve lastCheckedDate when i last checked for emails so i can show only newly received emails.
For retrieving data from datastore i use observeLastCheckedDate() and i must call it with handler because if i dont i get:
java.lang.IllegalStateException: Cannot invoke observe on a background thread
Function observeLastCheckedDate() get called but while it finish(updates lastCheckedDate), workManager task is already finished with not-updated var lastchecked date.
In main class i avoid this problem by creating and invoking callback but here that does not work(it makes whole app freeze), so we somehow need to wait for that function to finish or get some new way of retreiving data from datastore.
class WorkerMan(private val mContext: Context, workerParameters: WorkerParameters) :
CoroutineWorker(mContext, workerParameters) {
private lateinit var settingsManager: SettingsManager
var lastCheckedDate: Long = 0
#SuppressLint("RestrictedApi", "CheckResult")
val email = inputData.getString("email")
val password = inputData.getString("password")
val token = inputData.getString("token")
fun saveLastCheckedDate(lastCheckedDate: Long) {
GlobalScope.launch {
settingsManager.storeLastCheckedDate(lastCheckedDate)
}
}
private fun observeLastCheckedDate() {
settingsManager.lastCheckedDateFlow.asLiveData().observe(
ProcessLifecycleOwner.get(),
{
lastCheckedDate = it
println("LASTCHECKEDDATE LOADED")
}
)
}
#SuppressLint("RestrictedApi", "WrongThread")
override suspend fun doWork(): Result {
withContext(Dispatchers.IO) {
settingsManager = SettingsManager(getApplicationContext())
var messageCounter = 0;
val handler = Handler(Looper.getMainLooper())
handler.post {
observeLastCheckedDate()
}
println("**************************************************************************")
println("**************************************************************************")
println("WorkManager: Work called")
println("WorkManager email: " + email)
println("WorkManager: Last Checked Moment : " + lastCheckedDate.toString())
println("WorkManager: Current Moment : " + Instant.now().toEpochMilli())
println("**************************************************************************")
println("**************************************************************************")
try {
val session = Session.getDefaultInstance(Properties())
val store = session.getStore("imaps")
store.connect(
"mail.metropolitan.ac.rs",
993,
email,
password
)
val inbox = store.getFolder("INBOX")
inbox.open(Folder.READ_ONLY)
val messages = inbox.search(
FlagTerm(Flags(Flags.Flag.SEEN), false)
)
Arrays.sort(
messages
) { m1: Message, m2: Message ->
try {
return#sort m2.sentDate.compareTo(m1.sentDate)
} catch (e: MessagingException) {
throw RuntimeException(e)
}
}
// println("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ")
// println("WorkManager Started")
// println("WorkMananager email: " + email)
// val current = LocalTime.now()
// println("WorkMananager time: " + current)
// println("Messages amount: " + messages.size)
// println("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ")
for (message in messages) {
Thread.sleep(1000)
messageCounter++
if (message.receivedDate.toInstant().toEpochMilli() >= lastCheckedDate) {
Thread.sleep(1000)
println("=====================================================")
println("NOTIFIKACIJA")
var title = ""
for (element in message.from) {
title += element.toString().substringAfter("<").substringBefore(">")
title += " "
}
println("Title :" + title)
println("Subject :" + message.subject)
println("Datum i vreme : " + message.receivedDate)
title.replace("[", "")
title.replace("]", "")
send(token, message.subject, title)
}
if (messageCounter > 10) {
break
}
}
saveLastCheckedDate(Instant.now().toEpochMilli())
println("=====================================================")
Log.d("WorkManager", "Job finished")
} catch (e: Exception) {
Log.d("WorkManager error", "doWork not executed")
Log.d("WorkManager error", "error: ")
Log.d("WorkManager error", e.printStackTrace().toString())
} catch (e: NetworkOnMainThreadException) {
Log.d("WorkManager error", "doWork not executed")
Log.d("WorkManager error", "NetworkOnMainThreadException: ")
Log.d("WorkManager error", e.toString())
}
}
return Result.Success();
}
}
fun send(to: String?, body: String?, title: String?): String? {
try {
val apiKey =
"***************************************"
val url = URL("https://fcm.googleapis.com/fcm/send")
val conn = url.openConnection() as HttpURLConnection
conn.doOutput = true
conn.requestMethod = "POST"
conn.setRequestProperty("Content-Type", "application/json")
conn.setRequestProperty("Authorization", "key=$apiKey")
conn.doOutput = true
val message = JSONObject()
message.put("to", to)
message.put("priority", "high")
val notification = JSONObject()
notification.put("title", title)
notification.put("body", body)
message.put("notification", notification)
val os = conn.outputStream
os.write(message.toString().toByteArray())
os.flush()
os.close()
val responseCode = conn.responseCode
println("\nSending 'POST' request to URL : $url")
println("Post parameters : $message")
println("Response Code : $responseCode")
println("Response Code : " + conn.responseMessage)
val `in` = BufferedReader(InputStreamReader(conn.inputStream))
var inputLine: String?
val response = StringBuffer()
while (`in`.readLine().also { inputLine = it } != null) {
response.append(inputLine)
}
`in`.close()
println(response.toString())
return response.toString()
} catch (e: Exception) {
Log.d("WorkManager error", "send not executed")
Log.d("WorkManager error", "error: ")
Log.d("WorkManager error", e.printStackTrace().toString())
} catch (e: NetworkOnMainThreadException) {
Log.d("WorkManager error", "send() not executed")
Log.d("WorkManager error", "NetworkOnMainThreadException: ")
Log.d("WorkManager error", e.toString())
}
return "error"
}
DataStore class:
class SettingsManager(context: Context) {
private val dataStore = context.createDataStore(name = "user_settings_preferencess")
companion object {
val ENABLE_NOTIFICATIONS = preferencesKey<Int>("ENABLE_NOTIFICATIONS")
val ENABLE_MAIL_NOTIFICATIONS = preferencesKey<Int>("ENABLE_MAIL_NOTIFICATIONS")
val LAST_CHECKED_DATE = preferencesKey<Long>("LAST_CHECKED_DATE")
}
//Store user data
suspend fun storeNotifications(enableNotifications: Int) {
dataStore.edit {
it[ENABLE_NOTIFICATIONS] = enableNotifications
}
}
suspend fun storeMailNotifications(enableMailNotifications: Int) {
dataStore.edit {
it[ENABLE_MAIL_NOTIFICATIONS] = enableMailNotifications
}
}
suspend fun storeLastCheckedDate(lastCheckedDate: Long) {
dataStore.edit {
it[LAST_CHECKED_DATE] = lastCheckedDate
}
}
val lastCheckedDateFlow: Flow<Long> = dataStore.data.map {
it[LAST_CHECKED_DATE] ?: 0
}
val enableNotificationsFlow: Flow<Int> = dataStore.data.map {
it[ENABLE_NOTIFICATIONS] ?: 1
}
val enableMailNotificationsFlow: Flow<Int> = dataStore.data.map {
it[ENABLE_MAIL_NOTIFICATIONS] ?: 1
}
}
That's a HUGE mess with threading for simple work. (Never make your thread sleep to wait for a value)
if you going to use coroutines in the worker class. SO DON'T DO THAT
there is an alternative CoroutineWorker to extend your class from it instead of Worker
it will provide you with suspending version of doWork() function
NOTE: remember to add the -ktx version of the work manager dependency
Razorpay callbacks is not working in fragment instead of activity using fragment please give a solution If anyone aware thanks in advance.
private fun startPayment() {
val activity: Activity = requireActivity()
val co = Checkout()
try {
val options = JSONObject()
options.put("name", "Vendor")
options.put("description", " for Order")
//You can omit the image option to fetch the image from dashboard
options.put("image", "https://rzp-mobile.s3.amazonaws.com/images/rzp.png")
options.put("currency", "INR")
val payment: String = "1"//getcart?.CartTotal.toString()
// amount is in paise so please multiple it by 100
//Payment failed Invalid amount (should be passed in integer paise. Minimum value is 100 paise, i.e. ₹ 1)
var total = payment.toDouble()
total = total * 100
options.put("amount", total)
val preFill = JSONObject()
preFill.put("email", "hell#gmail.com")
preFill.put("contact", "9898989898")
options.put("prefill", preFill)
co.open(requireActivity(), options)
} catch (e: Exception) {
Toast.makeText(activity, "Error in payment: " + e.message, Toast.LENGTH_SHORT).show()
e.printStackTrace()
}
}
override fun onPaymentSuccess(s: String?) {
toast("onPaymentSuccess")
Log.i(TAG, "onPaymentSuccess: $s")
}
override fun onPaymentError(i: Int, s: String?) {
Log.e(TAG, "error code "+i.toString()+" -- Payment failed "+s.toString())
try {
toast("Payment error please try again")
} catch (e : Exception) {
Log.e("OnPaymentError", "Exception in onPaymentError", e);
}
}
Amount should be in Integer and in paise.
implement result listener to the fragment host activity and override error and success function there.
in fragment you won't get Razorpay callback function, so you should implement PaymentResultListener or PaymentResultWithDataListener in activity and from activity you have call your fragment and do your api call for razor pay response.
in your Activity:
#Override
public void onPaymentSuccess(String s, PaymentData paymentData) {
try {
FeeFragment feeList = (FeeFragment) mViewPager.getAdapter().instantiateItem(mViewPager, mViewPager.getCurrentItem());
feeList.checkRazorResponse(paymentData, true);
} catch (Exception e) {
Log.e("Exception in success", e.toString());
e.printStackTrace();
}
}
in your Fragment:
public void checkRazorResponse(PaymentData paymentData, boolean success) {
if (success) {
updatePaymentStatus(paymentData);
//do your api call
} else {
//handle error message
}
}
Razorpay payment integration is not supported on the fragment. You have to implement it on Activity.
Like this way
This code is for fragment:
binding.tvPlaceOrder.setOnClickListener(view -> {
startActivity(new Intent(getActivity(), PaymentActivity.class));
}
}
});
This code is for Activity:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityPaymentBinding.inflate(layoutInflater)
setContentView(binding.root)
payOnline()
}
private fun payOnline() {
val checkout = Checkout()
checkout.setKeyID(getString(R.string.razorpay_api_key))
checkout.setImage(R.mipmap.ic_launcher)
try {
val options = JSONObject()
options.put("name", "mName")
options.put("currency", "INR")
options.put("image", R.mipmap.ic_launcher)
options.put("amount", 10000) //pass amount in currency subunits
options.put("prefill.email", "roydeveloper01#gmail.com")
options.put("prefill.contact", "8620828385")
checkout.open(this, options)
} catch (e: Exception) {
Toast.makeText(this, "Error in starting Razorpay Checkout: $e", Toast.LENGTH_LONG).show()
}
}
override fun onPaymentSuccess(p0: String?) {
try {
binding.tvId.text = "Payment Successful \n Transaction ID $p0"
} catch (e: NoSuchAlgorithmException) {
binding.tvId.text ="Exception"
}
}
override fun onPaymentError(p0: Int, p1: String?) {
try {
binding.tvId.text ="Exception: $p1"
} catch (e: NoSuchAlgorithmException) {
e.printStackTrace()
}
binding.tvId.text ="Error: $p1"
}
My problem is solved this way. I think your problem will also solve . Best of luck.
I decided to wrap getting device location (once, without updating) using kotlin coriutines, so finally i got this code:
#SuppressLint("MissingPermission")
suspend fun LocationManager.getCurrentLocationOnce(): Location {
return suspendCancellableCoroutine { continuation ->
try {
val locationListener = object : SimpleLocationListener {
override fun onLocationChanged(location: Location?) {
if (location == null) {
this#getCurrentLocationOnce.removeUpdates(this)
continuation.resumeWithException(FailedToRetrieveLocationException("Location is NULL"))
} else {
this#getCurrentLocationOnce.removeUpdates(this)
continuation.resume(location)
}
}
override fun onProviderEnabled(provider: String?) {}
override fun onProviderDisabled(provider: String?) {
this#getCurrentLocationOnce.removeUpdates(this)
continuation.resumeWithException(ProviderDisabledException(provider ?: ""))
}
}
this.requestSingleUpdate(
LocationManager.GPS_PROVIDER,
locationListener,
null
)
} catch (e : Exception) {
continuation.resumeWithException(e)
}
}
}
When GPS is ON all works fine, but when GPS is OFF program fails with exception ProviderDisabledException, thats because of:
override fun onProviderDisabled(provider: String?) {
this#getCurrentLocationOnce.removeUpdates(this)
continuation.resumeWithException(ProviderDisabledException(provider ?: ""))
}
But i don't know why it's fails, because in place where i'm using this function i've got:
try {
val locationManager = (requireActivity().getSystemService(Context.LOCATION_SERVICE) as? LocationManager)
?: throw FailedToRetrieveLocationException("Location Service is null")
val location = locationManager.getCurrentLocationOnce()
log("[GOOGLE] downloadRestaurantsWithGPSLocationGoogle",
"Successfully got location={lat:${location.latitude}, long:${location.longitude}}")
downloadRestaurantsWithLocation(location)
} catch (ex : FailedToRetrieveLocationException) {
logError("[GOOGLE] downloadRestaurantsWithGPSLocationGoogle", ex)
throw ex
} catch (providerException : ProviderDisabledException) {
logError("[GOOGLE] downloadRestaurantsWithGPSLocationGoogle", providerException)
throw providerException
} catch (e : Exception) {
logError("[GOOGLE] downloadRestaurantsWithGPSLocationGoogle", e)
throw e
}
So i'm logging exception and rethrow it to caller function and in caller function i'm catching this exception:
try {
log("[GOOGLE] downloadRestaurants", "Starting donwload restaurants for GOOGLE")
downloadRestaurantsWithGPSLocationGoogle()
} catch (e : Exception) {
logError("[GOOGLE] error happened while getting location", e)
downloadRestaurantsWithFusedLocationGoogle()
}
And in error stacktrace i've got only this:
E/[GOOGLE]Â downloadRestaurantsWithGPSLocationGoogle: my.package.location.exceptions.ProviderDisabledException: Provider gps disabled
at my.package.common.location.LocationUtilsKt$getCurrentLocationOnce$$inlined$suspendCancellableCoroutine$lambda$1.onProviderDisabled(LocationUtils.kt:45)
at android.location.LocationManager$ListenerTransport._handleMessage(LocationManager.java:384)
at android.location.LocationManager$ListenerTransport.access$000(LocationManager.java:300)
at android.location.LocationManager$ListenerTransport$1.handleMessage(LocationManager.java:316)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:207)
at android.app.ActivityThread.main(ActivityThread.java:6878)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:547)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:876)
I don't know why app fails, because code like this works perfect:
lifecycleScope.launch {
try {
throwError()
} catch (e : Exception) {
e.printStackTrace()
}
}
private suspend fun throwError() {
return suspendCancellableCoroutine { continuation ->
continuation.resumeWithException(ProviderDisabledException("TEST"))
}
}
So, finally i realized why it's crash the app =). All ok with coroutines.
Problem is in this method:
#Throws(ProviderDisabledException::class, FailedToRetrieveLocationException::class)
private fun downloadRestaurantsWithGPSLocationGoogle() = lifecycleScope.launch {
log("[GOOGLE] downloadRestaurantsWithGPSLocationGoogle", "Trying to get location via GPS")
try {
val locationManager = (requireActivity().getSystemService(Context.LOCATION_SERVICE) as? LocationManager)
?: throw FailedToRetrieveLocationException("Location Service is null")
val location = locationManager.getCurrentLocationOnce()
log("[GOOGLE] downloadRestaurantsWithGPSLocationGoogle",
"Successfully got location={lat:${location.latitude}, long:${location.longitude}}")
downloadRestaurantsWithLocation(location)
} catch (ex : FailedToRetrieveLocationException) {
ex.printStackTrace()
logError("[GOOGLE] downloadRestaurantsWithGPSLocationGoogle", ex)
throw ex
} catch (providerException : ProviderDisabledException) {
providerException.printStackTrace()
logError("[GOOGLE] downloadRestaurantsWithGPSLocationGoogle", providerException)
throw providerException
} catch (e : Exception) {
e.printStackTrace()
logError("[GOOGLE] downloadRestaurantsWithGPSLocationGoogle", e)
throw e
}
}
And the problem is that i'm throwing exception from coroutine and handle this exception not in coroutine, so i'm launched my coroutine and all try-cathces are skipped, because here i'm using fire and forget style. So to fix this i need to do this method suspend and throw exceptions.
Place where trying to catch errors:
private fun downloadRestaurants() = lifecycleScope.launch {
log("downloadRestaurantsWithLocationSort",
"Requesting Manifest.permission.ACCESS_COARSE_LOCATION & Manifest.permission.ACCESS_FINE_LOCATION permissions")
val user = requestPermissions(
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION
)
if (!user.any { !it.second }) {
// permission is granted, can download restaurants and sort by nearest
log("downloadRestaurantsWithLocationSort", "Permissions is granted")
log("MANUFACTURER", Build.MANUFACTURER)
if (Build.MANUFACTURER == "Huawei" || Build.MANUFACTURER == "HUAWEI") {
showToast("HUAWEI")
try {
log("[HUAWEI] downloadRestaurants", "Starting donwload restaurants for HUAWEI")
downloadRestaurantsWithGPSLocationHuawei()
} catch (e : Exception) { // this will not work, because FIRE and FORGET
e.printStackTrace()
logError("[HUAWEI] error happened while getting location", e)
mainViewModel.downloadRestaurantsHeaders(null)
}
} else {
showToast("NOT A HUAWEI")
try {
log("[GOOGLE] downloadRestaurants", "Starting donwload restaurants for GOOGLE")
downloadRestaurantsWithGPSLocationGoogle()
} catch (e : Exception) { // this will not work, because FIRE and FORGET
e.printStackTrace()
logError("[GOOGLE] error happened while getting location", e)
downloadRestaurantsWithFusedLocationGoogle()
}
}
} else {
// permission is not granted, just download the restaurants
log("downloadRestaurantsWithLocationSort", "Permissions is NOT granted")
mainViewModel.downloadRestaurantsHeaders(null)
}
}
So the answer make functions downloadRestaurantsWithGPSLocationGoogle and downloadRestaurantsWithFusedLocationGoogle suspend and don't launch separate coroutine inside them. (remove lifecycleScope.launch)