In my android app I am using workerManger to upload some data to my server every 15 minutes, and then i decided to test it on 4 devices:
1.Huawei Honor 4x (android: 5.1.1, api level: 22)
2.Samsung Galaxy A8 2016 (android: 8.0.0 , api level: 26 )
3.Samsung A30 (android: 10 , api level: 29 )
4.Realme 5 pro (android: 10 , api level: 29 )
And sadly every device had a different behavior
1.The Huawei phone: uploaded the data to the server every hour (not every 15 minutes)
2.The Samsung a8 phone: works fine
3.The Samsung a30 phone: repeatedly stops uploading for some time and then continue
4.The Realme phone: works fine for the first 10 loops of so and then stops working
All of this with the app running and I have not rebooted the phones or even killed the app, so i am feeling kind of lost and do not know how to deal with this widely different behavior, I have even tried to use Stetho (which enable chrome dev tools for your app) to look at the workManger's database but i didn't find the database.
Here is my worker
const val NETWORK_URL = "URL"
const val REQUEST_METHOD = "REQUEST_METHOD"
const val REQUEST_BODY = "REQUEST_BODY"
private const val TAG = "NetworkWorker"
class NetworkWorker(appContext: Context, workerParams: WorkerParameters) :
Worker(appContext, workerParams) {
private lateinit var mUser: FirebaseUser
override fun doWork(): Result {
WorkManager.getInstance(applicationContext).pruneWork()
if (Firebase.auth.currentUser == null) {
Log.e(TAG, "doWork: user is null")
return Result.failure()
} else {
mUser = Firebase.auth.currentUser!!
}
val url = inputData.getString(NETWORK_URL)!!
val method = inputData.getInt(REQUEST_METHOD, Request.Method.GET)
val body = if (inputData.getString(REQUEST_BODY) == null) {
null
} else {
JSONObject(inputData.getString(REQUEST_BODY)!!)
}
Log.d(TAG, "doWork: body is $body")
val queue = Volley.newRequestQueue(applicationContext)
Log.d(TAG, "doWork: url is $url")
mUser.getIdToken(true)
.addOnCompleteListener { task ->
if (task.isSuccessful) {
val idToken = task.result!!.token
Log.d(TAG, "doWork: token is $idToken")
val request = object : JsonObjectRequest(method, url, body,
Response.Listener { response ->
Log.d(TAG, "doWork: $response")
},
Response.ErrorListener { error ->
Log.e(TAG, "doWork: error is", error)
}) {
override fun getHeaders(): MutableMap<String, String> {
val params = HashMap<String, String>()
params["Content-Type"] = "application/json"
params["Authorization"] = "$idToken"
return params
}
}
queue.add(request)
} else {
Log.e(
TAG,
"doWork: error getting the token",
task.exception
)
}
}
return Result.success()
}
}
and here is how I enqueue it in my activity
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
val uploadLocationWork = PeriodicWorkRequest
.Builder(NetworkWorker::class.java, 15, TimeUnit.MINUTES)
.setInputData(
workDataOf(
NETWORK_URL to "http://10.1.0.11:8000/api/location",
REQUEST_BODY to json,
REQUEST_METHOD to Request.Method.POST))
.setConstraints(constraints)
.build()
WorkManager
.getInstance(this)
.enqueueUniquePeriodicWork(
"testWorkManger",
ExistingPeriodicWorkPolicy.KEEP,
uploadLocationWork)
Related
With migration to kotlin, view model and recent changes in [kotlin test lib][1] I am working on issue with test.
I have a scenario:
request a web resource asynchronously
in case of error put the request in cache and update state with new pending request
All of this with help of kotlin flow and view model.
Scenario works well when executes on emulator, but fails when I run test for it. The issue is catch block of flow has not been triggered when error has thrown in flow.
Here is the code:
fun mintToken(to: String, value: Value, uri: String) {
logger.d("[start] mintToken()")
viewModelScope.launch {
repository.mintToken(to, value, uri)
.catch { it ->
if (it is TransactionException
&& it.message!!.contains("Transaction receipt was not generated after 600 seconds for transaction")) {
cacheRepository.createChainTx(to, value, uri) // TODO consider always put in pending cache and remove after it confirms as succeeded
val txReceipt = TransactionReceipt()
txReceipt.transactionHash = ""
emit(Response.Data(txReceipt))
} else {
emit(Response.Error.Exception(it))
}
}
.flowOn(Dispatchers.IO)
.collect {
logger.d(it.toString())
when (it) {
is Response.Data -> {
if (it.data.transactionHash.isEmpty()) {
state.update {
it.copy(
status = Status.MINT_TOKEN,
pendingTx = it.pendingTx + Transaction(to, value, uri)
)
}
}
}
is Response.Error.Message -> {
val errorMsg = "Something went wrong on mint a token with error ${it.msg}"
logger.d(errorMsg)
state.update {
val newErrors = it.errors + "Something went wrong on mint a token with error ${errorMsg}"
it.copy(status = Status.MINT_TOKEN, errors = newErrors)
}
}
is Response.Error.Exception -> {
logger.e("Something went wrong on mint a token ${to}, ${value}, ${uri}", it.error)
state.update {
val newErrors = it.errors + "Something went wrong on mint a token ${to}, ${value}, ${uri}"
it.copy(status = Status.MINT_TOKEN, errors = newErrors)
}
}
}
}
}
logger.d("[end] mintToken()")
}
#Throws(TransactionException::class)
override fun mintToken(to: String, value: Value, uri: String): Flow<Response<TransactionReceipt>> {
return flow {
throw TransactionException(
"Transaction receipt was not generated after 600 seconds for transaction",
"")
}
}
Test code for this is:
#get:Rule
var instantExecutorRule = InstantTaskExecutorRule()
// Set the main coroutines dispatcher for unit testing.
#ExperimentalCoroutinesApi
#get:Rule
var mainCoroutineRule = MainCoroutineRule()
private lateinit var subj: WalletViewModel
#Test
fun `when mintToken() is called with correct values, timeout exception is returned and pending tx are updated with new value`() = runTest {
val to = "0x6f1d841afce211dAead45e6109895c20f8ee92f0"
val url = "https://google.com"
val testValue = Value(
"Software Development",
BigInteger.valueOf(1000L),
BigInteger.valueOf(2000L),
false,
BigInteger.valueOf(0)
)
subj.mintToken(to, testValue, url)
assertThat(
"There is no pending transaction after mint a new token with timeout error",
subj.uiState.value.pendingTx.isNotEmpty()
)
}
Test code differs from dev code by replacing dispatcher in MainCoroutineRule and using kotlin construction runTest {}. How does it affect this case? Does issue case lays in some other place?
[1]: https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-test/MIGRATION.md
I try to update application in background. My code:
suspend fun installApk(file: File) {
withContext(Dispatchers.IO) {
if (extractVersion(file.absolutePath) <= BuildConfig.VERSION_CODE) {
return#withContext
}
val out = params.output
file.inputStream().copyTo(out)
params.session.fsync(out)
out.close()
val intent = Intent(ContextManager.retrieveActivityContext().get(), MainActivity::class.java)
intent.action = "android.intent.action.MAIN"
val pendingIntent = PendingIntent.getActivity(
ContextManager.retrieveActivityContext().get(),
params.sessionID,
intent,
0
)
// The app gets killed after installation session commit
params.session.commit(
pendingIntent.intentSender
)
}
}
private fun getOutputInstallStream(context: Context): APKSyncData {
with(context) {
val packageInstaller = packageManager.packageInstaller
val params =
PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL)
params.setAppPackageName(packageName)
val sessionId = packageInstaller.createSession(params)
val session = packageInstaller.openSession(sessionId)
return APKSyncData(
session = session,
sessionID = sessionId,
output = session.openWrite(packageName, 0, -1)
)
}
}
data class APKSyncData(
val session: Session, val sessionID: Int, val output: OutputStream
)
On some devices(API 22 for example) in onNewIntent I receive status STATUS_PENDING_USER_ACTION.
I found on google and run this code:
when(status){
PackageInstaller.STATUS_PENDING_USER_ACTION -> {
val confirmIntent = extras?.get(Intent.EXTRA_INTENT) as Intent
startActivity(confirmIntent)
}
}
But the application has no interaction with the user, everything happens through the web socket.
On device with API 24 this works perfectly. For API 22 or 23 , the emulator does not allow installing the file from local storage. I'm not trying to run the application from android studio, it's a release build.
Question: for API 22 is it possible to confirm programmatically, or to skip this step and move on?
I am using Spotify API to login user to the app. this is the interface i wrote per documentation:
interface API {
#GET("/authorize")
fun login(#Query("client_id") client_id:String,
#Query("response_type") response_type:String,
#Query("redirect_uri")redirect_uri:String,
#Query("scope") scope:String
):Call<LoginResult>
This is the response result data class:
data class LoginResult(
val code: String
)
And this is the login function:
fun login() {
val BASE_URL = "https://accounts.spotify.com"
val CLIENT_ID = "c6c23e3e2f604f9aa1780fe7504e73c6"
val REDIRECT_URI = "com.example.myapp://callback"
val retrofit: Retrofit = Retrofit.Builder().baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create()).build()
val service: API = retrofit.create(API::class.java)
val listCall: Call<LoginResult> =
service.login(CLIENT_ID, "code", REDIRECT_URI, "user-top-read")
listCall.enqueue(object : Callback<LoginResult> {
override fun onResponse(response: Response<LoginResult>?, retrofit: Retrofit?) {
if (response?.body() != null) {
Log.i("result!", response.body().code)
}
if(response?.body() == null){
Log.i("Code" , response!!.code().toString())
Log.i("Response! ", "null response body")
}
}
override fun onFailure(t: Throwable?) {
Log.e("Here", "it is")
Log.e("Error", t!!.message.toString())
}
})
}
But I am getting this error:
E/Here: it is
E/Error: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 2 column 1 path $
There are a lot of questions here about this particular error and I read all of them and tried to implement the suggested solutions, but none worked.
Any help would be appreciated.
[this is the mentioned documentation link]
(https://developer.spotify.com/documentation/general/guides/authorization/code-flow/)
I am trying authenticate user with spotify app and spotify auth API (implementation 'com.spotify.android:auth:2.0.1') followed the steps mentioned in Spotify SDK github sample
my code:
Added in gradle(app.module) defaultConfig { manifestPlaceholders = [redirectSchemeName: "appname", redirectHostName:"spotify_login_callback"] }
// Fragment/Activity
val CLIENT_ID = "7bf56252cd644b339cc97df5b4d7eeee"
val AUTH_TOKEN_REQUEST_CODE = 0x10
val AUTH_CODE_REQUEST_CODE = 0x11
var mAccessToken: String? = null
var mAccessCode: String? = null
fun onRequestTokenClicked() {
val request = getAuthenticationRequest(AuthorizationResponse.Type.TOKEN)
AuthorizationClient.openLoginActivity(requireActivity(), AUTH_TOKEN_REQUEST_CODE, request)
}
fun onRequestCodeClicked() {
val request: AuthorizationRequest =
getAuthenticationRequest(AuthorizationResponse.Type.CODE)
AuthorizationClient.openLoginActivity(requireActivity(), AUTH_CODE_REQUEST_CODE, request)
}
private fun getAuthenticationRequest(type: AuthorizationResponse.Type): AuthorizationRequest {
return AuthorizationRequest.Builder(
CLIENT_ID,
type,
getRedirectUri().toString()
)
.setShowDialog(false)
// "user-read-email"
.setScopes(arrayOf("user-read-email")) // user-read-private , "streaming"
.build()
}
private fun getRedirectUri(): Uri? {
return Uri.Builder()
.scheme("appname")
.authority("spotify_login_callback")
.build()
}
val response = AuthorizationClient.getResponse(resultCode, data)
if (response.error != null && !response.error.isEmpty()) {
setResponse(response.error)
Toast.makeText(requireActivity(),"Error: response.error"+response.error,Toast.LENGTH_SHORT).show()
}
if (requestCode == AUTH_TOKEN_REQUEST_CODE) {
mAccessToken = response.accessToken
Toast.makeText(requireActivity(),"AccessToken: "+mAccessToken,Toast.LENGTH_SHORT).show()
updateTokenView()
} else if (requestCode == AUTH_CODE_REQUEST_CODE) {
mAccessCode = response.code
Toast.makeText(requireActivity(),"AccessCode"+mAccessCode,Toast.LENGTH_SHORT).show()
}
This code prints log "Spotify auth completing. The response is in EXTRA with key response" after debugging library gives AUTHENTICATION SERVICE UNKNOWN_ERROR does anyone know the cause of this error, same code provided in SDK sample works fine.
I have a problem: I'm writing a weather app, using retrofit 2.0. When I run the app on the emulator, everything works fine(API 24, 28, 29). But today I launched my app on a physical device (Galaxy A21s, version android 10) and the request to the server is not working. The request works onResponse() but it comes with response.body () = = null and response.is Successful == null. But everything works in the emulator!
Can you tell us what the problem is and how to solve it?
class DataProcessing {
private val retrofitImpl: RetrofitImpl = RetrofitImpl()
private val mainActivity = MainActivity()
internal fun sendRequest(townName:String, instance : DataProcessingCallback){
retrofitImpl.getRequest().showWeather(townName).enqueue(object : Callback<DateWeather> {
override fun onResponse(call: retrofit2.Call<DateWeather>, response: Response<DateWeather>) {
if (response.isSuccessful && response.body() != null) {
processingData(response.body(), null, instance)
} else
processingData(null, Throwable("ответ не получен"), instance)
}
override fun onFailure(call: Call<DateWeather>, t: Throwable) {
Log.d("Main", "onFailure")
processingData(null, t, instance)
}
})
}
private fun processingData(dateWeather:DateWeather?, error: Throwable?, instance : DataProcessingCallback){
if (dateWeather == null || error != null) {
Log.d("Egor", "error: ${error!!.message.toString()}")
instance.showToastText("Произошла ошибка \n Возможно вы неправильно ввели название населенного пункта")
} else {
if (dateWeather == null) Log.d("Main", "Loose")
else {
val string = dateWeather.weather.get(0).toString()
val size = string.length - 1
instance.onSuccessfulDataProcessed(string.subSequence(13, size).toString(), dateWeather.main.temp!!.toInt())
}
}
}
}
interface ShowWeather{
#GET("weather?&appid=(TOKEN)&units=metric")// there is a token here, I just deleted it when publishing, everything is fine with it
fun showWeather(#Query("q") town: String): Call<DateWeather>
}
class RetrofitImpl{
fun getRequest() : ShowWeather{
val retrofitBuilder = Retrofit.Builder()
.baseUrl("http://api.openweathermap.org/data/2.5/")
.addConverterFactory(GsonConverterFactory.create())
.build()
return retrofitBuilder.create(ShowWeather::class.java)
}
}
data class DateWeather(
val main: Main,
val weather : List<Weather>
)
data class Main(
val temp : Double?
)
data class Weather(
val main: String
)
At a base URL, you are trying with http unsecured connection. Can you check with "https://" instead of "http://"
val retrofitBuilder = Retrofit.Builder()
.baseUrl("https://api.openweathermap.org/data/2.5/")
.addConverterFactory(GsonConverterFactory.create())
.build()
The error turned out to be elementary: on a physical device, I used a hint (T9) that returned the city. After the name of the city there was a gap and this was the error. trim () solved my problem.
#Kishore A, thank you so much for your help!
townName = editText.getText().trim().toString()