I have a custom worker which has the job to fetch some contacts from an API, and then store these contacts in the contact list of the phone. This worker is still freezing the UI. Any help would be appreciated.
This is the Worker's doWork method:
override fun doWork(): Result {
return try {
makeStatusNotification("Saving Contacts", applicationContext, "WorkRequest Starting")
//MAIN METHOD CALL
if (!checkContactListEmpty()) {
removeAllContacts()
}
connectAndGetApiData()
val x = Data.Builder()
Result.success(x.build())
} catch (e: Exception) {
Log.e("NoWork","Unable to save image to Gallery $e")
Result.failure()
}
}
This is the connectAndGetApi method:
fun connectAndGetApiData() {
val BASE_URL = "HTTP_URL"
val res = Data.Builder()
allNames = object : ArrayList<String>(){}
allNumbers = object : ArrayList<String>(){}
Log.d("entering", "connecting")
if (retrofit == null) {
retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addConverterFactory(ScalarsConverterFactory.create())
.build()
}
val contactsApiService: RestApi? = retrofit?.create(RestApi::class.java)
val call: Call<ContactList>? = contactsApiService?.getAllContactDetails()
call?.enqueue(object : Callback<ContactList> {
override fun onResponse(
call: Call<ContactList>,
response: Response<ContactList>
) {
Log.d("AllContacts",response.message())
val contactList: ContactList = response.body()
if(contactList!= null){
Log.d("AllContacts", contactList.allContacts.size.toString())
for(x in contactList.allContacts){
addContact(x.name,x.phoneNo)
Thread.sleep(50)
}
}else{
Log.d("AllContacts", "contacts null")
}
}
override fun onFailure(
call: Call<ContactList>,
throwable: Throwable
) {
val TAG = "AllContacts"
Log.e(TAG, throwable.toString())
}
})
}
The removeAllContacts() is standard function to remove all contacts
The Api results in a set of around 22000 contacts
Edited:
This is the addContact() method
private fun addContact(name:String?, number: String?) {
var finalName = ""
val finalNumber = number
if(name?.isEmpty() == true){
val tsLong = System.currentTimeMillis() / 1000
val ts = tsLong.toString()
finalName = "NoName$ts"
}else{
finalName = name.toString()
finalName = finalName.substring(1,(finalName.length - 2))
}
val ops = ArrayList<ContentProviderOperation>()
val rawContactID: Int = ops.size
ops.add(
ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
.withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, null)
.withValue(ContactsContract.RawContacts.ACCOUNT_NAME, null)
.build()
)
ops.add(
ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, rawContactID)
.withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, finalName)
.build()
)
ops.add(
ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, rawContactID)
.withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, finalNumber)
.withValue(ContactsContract.CommonDataKinds.Phone.TYPE, ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE)
.build()
//
)
try { // Executing all the insert operations as a single database transaction
Log.d(
"AddingContact", "Name: $finalName Number: $finalNumber"
)
applicationContext.contentResolver.applyBatch(ContactsContract.AUTHORITY, ops)
Thread.sleep(50)
Log.d("Contact Saved","Saved")
} catch (e: RemoteException) {
e.printStackTrace()
} catch (e: OperationApplicationException) {
e.printStackTrace()
}
}
Whilst this Retrofit call is done off of the MainThread, the callback is on the MainThread.
i.e. call?.enqueue(object : Callback<ContactList> {
anything you do in this callback will be on the UI Thread.
And you do this code:
for(x in contactList.allContacts){
addContact(x.name,x.phoneNo)
Thread.sleep(50)
}
Which is sleeping the UI thread for 50 milliseconds for every contact you have in that list. Which in the comments says has 22,000 items???
You'll need to use a background thread if you want to work with datasets that large.
A quick and dirty solution would be to do this:
Use Coroutines:
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.2"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.2"
Change Retrofit to use Coroutines:
interface ContactsApiService {
#GET("whateverYourEndPointIs")
suspend fun getAllApiDetails(): Response<ContactList>
}
Run it all on a background thread:
GlobalScope.launch {
val response: Response<ContactList> = contactsApiService?.getAllContactDetails()
Log.d("AllContacts",response.message())
val contactList: ContactList = response.body()
if (contactList!= null) {
Log.d("AllContacts", contactList.allContacts.size.toString())
for(x in contactList.allContacts){
addContact(x.name,x.phoneNo)
delay(50)
}
} else {
Log.d("AllContacts", "contacts null")
}
}
Related
I am trying to display data on two different fragments from viewmodels: Recent Races and Upcoming Races. i wrote a filter function to filter the races that are yet to start and the ones that finished. Upcoming races works perfectly, when there is a change in api endpoint it removes the race from upcoming races list. but the problem is that it wont add it to recent races.
here is my code in RecentRacesViewModel
private fun getDetails() {
getRaceDetailsUseCase().onEach { result ->
when (result) {
is Resource.Success -> {
val filteredList = result.data.filter {
val time = Calendar.getInstance().time
val formatterCurrentTime = SimpleDateFormat("yyyy-MM-dd")
val formatterNow = DateTimeFormatter.ofPattern("yyyy-MM-dd")
val currentTime = formatterCurrentTime.format(time)
val dateNow = LocalDate.parse(currentTime, formatterNow)
val dateFromModel = it.date
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd")
val date = LocalDate.parse(dateFromModel, formatter)
dateNow >= date
}
_state1.value = Resource.Success(filteredList)
}
is Resource.Error -> {
_state1.value = Resource.Error("woops!")
}
is Resource.Loading -> {
_state1.value = Resource.Loading(true)
}
}
}.launchIn(viewModelScope)
}
thanks for help
Edit: adding the UseCase:
class RaceDetailsUseCase #Inject constructor(
private val repository: RaceResultsRepository
) {
operator fun invoke(): Flow<Resource<List<RaceDomain>>> = flow {
try {
emit(Resource.Loading(true))
val raceData = repository.GetRaceResultsRepository()
emit(Resource.Success(raceData))
} catch (e: HttpException) {
Log.d("tag", "error")
} catch (e: IOException) {
Log.d("tag", "io error")
}
}
}
In this app, I used Blogger API, I have this method that gets posts
#GET
suspend fun getPostList(#Url URL: String): Response<PostList>
This is the base URL that I used with it
const val BASE_URL = "https://www.googleapis.com/blogger/v3/blogs/$BLOG_ID/posts/"
when at first request it become like this
https://www.googleapis.com/blogger/v3/blogs/12345678910/posts/maxResults=20&key=API_KEY
when the second request it should be getting a token parameter pageToken=ABCDE in result that I needed to do pagination, so I edit the BASE URL in viewModel and add the pageToken parameter to it then I pass it to the getPostList method again
and it looks like this
https://www.googleapis.com/blogger/v3/blogs/12345678910/posts/maxResults=20&pageToken=ABCDEFG&key=API_KEY
This my network layer
private val interceptor : HttpLoggingInterceptor = HttpLoggingInterceptor().apply {
this.level = HttpLoggingInterceptor.Level.BODY
}
#Singleton
#Provides
fun postAPIService(): PostAPIService {
return Retrofit.Builder()
.baseUrl(Constants.BASE_URL)
.client(
OkHttpClient.Builder().readTimeout(
15, TimeUnit.SECONDS
).connectTimeout(15, TimeUnit.SECONDS)
.addNetworkInterceptor(interceptor)
.addInterceptor { chain ->
val url = chain
.request()
.url
.newBuilder()
.addQueryParameter("maxResults", Constants.MAX_RESULT)
.addQueryParameter("key", Constants.API_KEY)
.build()
chain.proceed(chain.request().newBuilder().url(url)
.build())
}.build()
)
.addConverterFactory(GsonConverterFactory.create())
.build().create(PostAPIService::class.java)
}
}
In view model layer:
/** RETROFIT **/
var postsResponse: MutableLiveData<NetworkResult<PostList>> = MutableLiveData()
var postListResponse: PostList? = null
val label = MutableLiveData<String>()
var finalURL: MutableLiveData<String> = MutableLiveData()
private val token = MutableLiveData<String?>()
private suspend fun getPostsSafeCall() {
postsResponse.postValue(NetworkResult.Loading())
Log.e(TAG, "getPostsSafeCall finalURL is ${finalURL.value!!}")
if (hasInternetConnection()) {
try {
if (finalURL.value.isNullOrEmpty()) {
finalURL.postValue(BASE_URL)
}
val response = mainRepository.remoteDataSource.getPostList(finalURL.value!!)
postsResponse.value = handlePostsResponse(response)
} catch (exception: Exception) {
postsResponse.postValue(NetworkResult.Error(exception.message.toString()))
// Log.e(TAG, e.message + e.cause)
if (exception is HttpException) {
errorCode.postValue(exception.code())
Log.e(TAG, "getPostsSafeCall: errorCode $errorCode")
Log.e(TAG, "getPostsSafeCall: ${exception.message.toString()}")
}
}
} else {
postsResponse.postValue(NetworkResult.Error("No Internet Connection."))
}
}
private fun handlePostsResponse(response: Response<PostList>): NetworkResult<PostList> {
if (response.isSuccessful) {
token.value = response.body()?.nextPageToken
Log.d(TAG, "handlePostsResponse: token = ${response.body()?.nextPageToken.toString()}")
response.body()?.let { resultResponse ->
Log.d(
TAG, "handlePostsResponse: old token is: ${token.value} " +
"new token is: ${resultResponse.nextPageToken}"
)
finalURL.postValue(
BASE_URL + "&pageToken=${token.value}"
)
if (postListResponse == null) {
postListResponse = resultResponse
} else {
val oldPosts = postListResponse?.items
val newPosts = resultResponse.items
oldPosts?.addAll(newPosts)
}
Log.d(TAG, "handlePostsResponse: ${token.value}")
Log.e(TAG, "handlePostsResponse finalURL is ${finalURL.value!!}")
for (item in resultResponse.items) {
insertItem(item)
}
return NetworkResult.Success(postListResponse ?: resultResponse)
}
} else {
if (token.value == null) {
errorCode.postValue(400)
} else {
errorCode.postValue(response.code())
}
Log.d(TAG, "handlePostsResponse: ${response.code().toString()}")
Log.d(TAG, "handlePostsResponse: ${response.headers().toString()}")
Log.d(TAG, "handlePostsResponse: ${response.headers().toString()}")
// Log.d(TAG, "handlePostsResponse: final ${finalURL.value.toString()}")
return NetworkResult.Error(
"network results of handlePostsResponse ${response.body().toString()}"
)
}
return NetworkResult.Error(
"network results of handlePostsResponse ${response.body().toString()}"
)
}
WHAT I NEED
A way to inject pageToken parameter dynamically only when there's a value (NOT NULL OR EMPTY) to avoid the following in final/base URL
https://www.googleapis.com/blogger/v3/blogs/4294497614198718393/posts/?maxResults=20&pageToken=null&key=API_KEY
this cause error 400 badRequest
{
"error": {
"code": 400,
"message": "We're sorry, but the value for field pageToken was not valid.",
"errors": [
{
"message": "We're sorry, but the value for field pageToken was not valid.",
"domain": "global",
"reason": "invalid"
}
]
}
}
I tried edit the PostAPIService method to look like that
#GET("posts")
suspend fun getPostList(#Query("pageToken") pageToken: String=""): Response<PostList>
and use it in viewModel like this
val response = if (token.value.isNullOrEmpty()) {
mainRepository.remoteDataSource.getPostList()
} else {
mainRepository.remoteDataSource.getPostList(token.value!!)
}
but unfortunately this causes badRequest also because of pageToken=null in BASE_URL
As docs said Retrofit ignores null query values, it means that you just need to remove pageToken from your base url and make corresponding parameter nullable in your function:
#GET("posts")
suspend fun getPostList(#Query("pageToken") pageToken: String? = null): Response<PostList
So I have an Android Job which handle network request.
This job can started in many ways, so it can run easily parralel, which is bad for me.
I would like to achive that, the job don't started twice, or if it started, than wait before the try catch block, until the first finishes.
So how can I to reach that, just only one object be/run at the same time.
I tried add TAG and setUpdateCurrent false, but it didn't do anything, so when I started twice the job, it rung parallel. After that I tried mutex lock, and unlock. But it did the same thing.
With mutex, I should have to create an atomic mutex, and call lock by uniq tag or uuid?
Ok, so I figured it out what is the problem with my mutex, the job create a new mutex object every time, so it never be the same, and it never will wait.
My Job:
class SendCertificatesJob #Inject constructor(
private val sendSync: SendSync,
private val sharedPreferences: SharedPreferences,
private val userLogger: UserLogger,
private val healthCheckApi: HealthCheckApi
) : Job(), CoroutineScope {
override val coroutineContext: CoroutineContext
get() = Dispatchers.IO
private val countDownLatch = CountDownLatch(1)
private val mutex = Mutex()
override fun onRunJob(params: Params): Result {
var jobResult = Result.SUCCESS
if (!CommonUtils.isApiEnabled(context))
return jobResult
val notificationHelper = NotificationHelper(context)
var nb: NotificationCompat.Builder? = null
if (!params.isPeriodic) {
nb = notificationHelper.defaultNotificationBuilder.apply {
setContentTitle(context.resources.getString(R.string.sending_certificates))
.setTicker(context.resources.getString(R.string.sending_certificates))
.setOngoing(true)
.setProgress(0, 0, true)
.setSmallIcon(android.R.drawable.stat_notify_sync)
.setLargeIcon(
BitmapFactory.decodeResource(
context.resources,
R.mipmap.ic_launcher
)
)
}
notificationHelper.notify(NOTIFICATION_ID, nb)
jobCallback?.jobStart()
}
val failureCount = params.failureCount
if (failureCount >= 3) {
nb?.setOngoing(false)
?.setContentTitle(context.resources.getString(R.string.sending_certificates_failed))
?.setTicker(context.resources.getString(R.string.sending_certificates_failed))
?.setProgress(100, 100, false)
?.setSmallIcon(android.R.drawable.stat_sys_warning)
notificationHelper.notify(NOTIFICATION_ID, nb)
return Result.FAILURE
}
GlobalScope.launch(Dispatchers.IO) {
mutex.lock()
userLogger.writeLogToFile("SendCertificatesJob.onRunJob(), date:" + Calendar.getInstance().time)
try {
//Test
var doIt = true
var count =0
while (doIt){
Timber.d("SendSyncWorker: $count")
count++
delay(10000)
if(count == 12)
doIt = false
}
healthCheckApi.checkHealth(ApiModule.API_KEY).await()
try {
sendSync.syncRecordedClients()
} catch (e: Exception) {
e.printStackTrace()
}
val result = sendSync().forEachParallel2()
result.firstOrNull { it.second != null }?.let { throw Exception(it.second) }
val sb = StringBuilder()
if (nb != null) {
nb.setOngoing(false)
.setContentTitle(context.resources.getString(R.string.sending_certificates_succeeded))
.setTicker(context.resources.getString(R.string.sending_certificates_succeeded))
.setProgress(100, 100, false)
.setStyle(NotificationCompat.BigTextStyle().bigText(sb.toString()))
.setSmallIcon(android.R.drawable.stat_notify_sync_noanim)
notificationHelper.notify(NOTIFICATION_ID, nb)
jobCallback?.jobEnd()
}
sharedPreferences.edit().putLong(KEY_LATEST_CERTIFICATES_SEND_DATE, Date().time)
.apply()
} catch (e: Exception) {
Timber.tag(TAG).e(e)
if (nb != null) {
nb.setOngoing(false)
.setContentTitle(context.resources.getString(R.string.sending_certificates_failed))
.setTicker(context.resources.getString(R.string.sending_certificates_failed))
.setProgress(100, 100, false)
.setSmallIcon(android.R.drawable.stat_sys_warning)
notificationHelper.notify(NOTIFICATION_ID, nb)
jobCallback?.jobEnd()
}
jobResult = Result.RESCHEDULE
} finally {
countDownLatch.countDown()
mutex.unlock()
}
}
countDownLatch.await()
return jobResult
}
Job schedule:
fun scheduleNowAsync(_jobCallback: JobCallback? = null) {
jobCallback = _jobCallback
JobRequest.Builder(TAG_NOW)
.setExecutionWindow(1, 1)
.setBackoffCriteria(30000, JobRequest.BackoffPolicy.LINEAR)
.setRequiredNetworkType(JobRequest.NetworkType.CONNECTED)
.setRequirementsEnforced(true)
.setUpdateCurrent(true)
.build()
.scheduleAsync()
}
fun schedulePeriodicAsync() {
jobCallback = null
JobRequest.Builder(TAG)
.setPeriodic(900000)
.setRequiredNetworkType(JobRequest.NetworkType.CONNECTED)
.setRequirementsEnforced(true)
.setUpdateCurrent(true)
.build()
.scheduleAsync()
}
I found the solution for my problem.
So because I use dagger, I provided a singleton Mutex object, and injected into the job. When the job starts call mutex.lock(), and beacuse there is only 1 object from the mutex, even if another job starts, the second job will waite until the firsjob is done.
I'm trying to upload a file to Google Drive using Google Drive REST API v3. After the upload process is completed, it returns a status code of 200 (successful). But I can't find the files in my Google Drive. Please tell me what am I doing wrong? I will really appreciate if you provide a proper illustration or better still code snippet while helping me with this problem of mine. I am really anticipating your answers.
I have tried following the documentation but I am still getting the same error. I have searched everywhere online and stackoverflow, but none seems to provide the solution to my problem.
here is the code
private val AUTHORIZATION_PARAM = "Authorization"
private val BEARER_VAL = "Bearer "
private val CONTENT_TYPE_PARAM = "Content-Type: "
private val LINE_FEED = "\r\n"
private val APP_FOLDER_ID = "appDataFolder"
fun connectAndStartOperation() {
if (mAuthCode == null) {
signInOptions = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestEmail()
.requestProfile()
.requestScopes(Scope(Scopes.DRIVE_APPFOLDER))
.requestIdToken(resources.getString(R.string.gdrive_clientId))
.requestServerAuthCode(resources.getString(R.string.gdrive_clientId))
.build()
mGoogleSignInClient = GoogleSignIn.getClient(this, signInOptions!!)
startActivityForResult(mGoogleSignInClient?.signInIntent, CLOUD_STORAGE)
Log.i("mAuthCode", "false")
} else {
Log.i("mAuthCode", "true")
writeDbToDrive()
mNextGoogleApiOperation = INVALID;
}
}
fun disconnect() {
mGoogleSignInClient?.signOut()
mActivity = null
mNextGoogleApiOperation = INVALID
mAuthCode = null
mAccessToken = null
mTokenExpired = 0
}
override fun onDestroy() {
disconnect()
super.onDestroy()
}
public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == CLOUD_STORAGE) {
val task = GoogleSignIn.getSignedInAccountFromIntent(data)
.addOnSuccessListener(this)
.addOnFailureListener(this)
}
}
override fun onSuccess(googleSignInAccount: GoogleSignInAccount?) {
Log.i("mAuthCode", "Success")
val scope = "oauth2:https://www.googleapis.com/auth/plus.me https://www.googleapis.com/auth/userinfo.profile"
idTokenString = googleSignInAccount?.idToken
mAuthCode = googleSignInAccount?.serverAuthCode
mGoogleSignInAccount = googleSignInAccount
doAsync {
try {
mAccessToken = GoogleAuthUtil.getToken(this#SettingsActivity, mGoogleSignInAccount?.account, scope)
} catch (e: Exception) {
Log.i("Error AccessToken", "${e.message}")
e.printStackTrace()
}
uiThread {
Log.i("AccessTokenMy", "$mAccessToken")
}
}
}
override fun onFailure(p0: java.lang.Exception) {
Log.i("mAuthCode", "Failed")
p0.printStackTrace()
}
private fun writeDbToDrive() {
var conn: HttpURLConnection? = null
var os: OutputStream? = null
val accessToken = requestAccessToken(mGoogleSignInAccount!!)
if (accessToken == null)
return
try {
val boundary = "pb" + System.currentTimeMillis()
val url = URL("https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart")
conn = url.openConnection() as HttpURLConnection
conn.requestMethod = "POST"
conn.useCaches = false
conn.doOutput = true
conn.doInput = true
conn.connectTimeout = 5000
conn.setRequestProperty(AUTHORIZATION_PARAM, BEARER_VAL + accessToken!!)
conn.setRequestProperty("Content-Type", "multipart/related; boundary=$boundary")
Log.i("Action", "Parameter set for server")
/////// Prepare data
//val timestamp = SimpleDateFormat("yyyy-MM-dd_HH:mm:ss", Locale.getDefault()).format(Date())
// Prepare file metadata (Change your backup file name here)
val b = StringBuilder()
b.append('{')
.append("\"name\":\"").append(exportedFileName).append('\"')
.append(',')
.append("\"mimeType\":").append("\"text\\/csv\"")
.append(',') //"\"application\\/vnd.google-apps.unknown\""
.append("\"parents\":").append("[\"").append(APP_FOLDER_ID).append("\"]")
.append('}')
val metadata = b.toString()
val data = readFile(File(filePath))
/////// Calculate body length
var bodyLength = 0
// MetaData part
b.setLength(0)
b.append("--").append(boundary).append(LINE_FEED)
b.append(CONTENT_TYPE_PARAM).append("application/json; charset=UTF-8")
.append(LINE_FEED)
b.append(LINE_FEED)
b.append(metadata).append(LINE_FEED)
b.append(LINE_FEED)
b.append("--").append(boundary).append(LINE_FEED)
b.append(CONTENT_TYPE_PARAM).append("text/csv").append(LINE_FEED)
b.append(LINE_FEED)
val beforeFilePart = b.toString().toByteArray(charset("UTF_8"))
bodyLength += beforeFilePart.size
bodyLength += data.size // File
b.setLength(0)
b.append(LINE_FEED)
b.append("--").append(boundary).append("--")
val afterFilePart = b.toString().toByteArray(charset("UTF_8"))
bodyLength += afterFilePart.size
conn.setRequestProperty("Content-Length", bodyLength.toString())
//if (BuildConfig.DEBUG_MODE) DebugHelper.log("LENGTH", bodyLength)
/////// Write to socket
os = conn.outputStream
try {
os!!.write(beforeFilePart)
os!!.write(data)
os!!.write(afterFilePart)
os!!.flush()
} catch (e: Exception) {
e.printStackTrace()
}
val msg = conn.responseMessage
val code = conn.responseCode
if (code == 200) {
Log.i("writeDbToDrive", "Exported Successfully: $code $msg")
} else {
Log.i("writeDbToDrive", "Error: $code $msg")
}
} catch (e: Exception) {
e.printStackTrace()
Log.i("writeDbToDrive", e.message!!)
} finally {
if (os != null) {
try {
os!!.close()
} catch (e: IOException) {
}
}
conn?.disconnect()
}
}
#Throws(IOException::class)
private fun readFile(file: File): ByteArray {
val f = RandomAccessFile(file, "r")
try {
val longlength = f.length()
val length = longlength.toInt()
if (length.toLong() != longlength)
throw IOException("File size >= 10 Mb")
val data = ByteArray(length)
f.readFully(data)
return data
} finally {
f.close()
}
}
private fun requestAccessToken(mGoogleSignInAccount: GoogleSignInAccount): String? {
val scope = "oauth2:https://www.googleapis.com/auth/plus.me https://www.googleapis.com/auth/userinfo.profile"
doAsync {
try {
mAccessToken = GoogleAuthUtil.getToken(this#SettingsActivity, mGoogleSignInAccount?.account, scope)
} catch (e: Exception) {
Log.i("Error AccessToken", "${e.message}")
e.printStackTrace()
}
uiThread {
Log.i("AccessTokenMy", "$mAccessToken")
}
}
return mAccessToken
}
After reading through this Files: create Documentation, I have finally fixed the problem. Unknown to me is that the files where being saved in the AppData folder created by my app. The AppData folder is hidden which can only be accessible by and through my app. For me to be able to save the file to My Drive folder, I removed the part of the metadata
` .append(',')
.append("\"parents\":").append("[\"").append(APP_FOLDER_ID).append("\"]")`
So the metadata part is now like this
val b = StringBuilder()
b.append('{')
.append("\"name\":\"").append(exportedFileName).append('\"')
.append(',')
.append("\"mimeType\":").append("\"text\\/csv\"")
.append('}')
val metadata = b.toString()
Every other thing remains the same
I am trying to to a fairly simple unit test with mockito but having some trouble with the callback. I have been exploring several alternatives and a few seem good including co-routines and the do answer methods with mockito. I have not been able to figure out a good solution yet, any ideas ?
Here is my test ;
//Test for add 5 minutes
#Test
public void testWithArgumentWithDummyReturnObject() throws ParseException {
ParkingMeter testZone = new ParkingMeter();
testZone.setZoneId("21788");
testZone.setMeterName("Train Lot");
Date startDate = dateFormat.parse("09/10/2018 1:00 PM");
Date endDate = dateFormat.parse("09/10/2018 1:05 PM");
Date expectedOutputDate = dateFormat.parse("09/10/2018 1:05 PM");
CurrentSessionsResponse fakeResponse = new CurrentSessionsResponse();
CurrentSession fakeSession = new CurrentSession("fake","","");
fakeSession.zone = "Fake 1";
ArrayList<CurrentSession> sessionlist = new ArrayList<>();
sessionlist.add(fakeSession);
fakeResponse.parkingSessions = sessionlist;
MockRepository mockRepo = mock(MockRepository.class);
doReturn(fakeResponse).when(mockRepo).getCurrentSessions();
ExtendedVariableRateUtil variableRateUtil = new ExtendedVariableRateUtil();
variableRateUtil.init(testZone,"", 360, mockRepo);
variableRateUtil.setTime(5);
assertThat(variableRateUtil.getDidJump(), is(false));
assertThat(variableRateUtil.getCurrentCustomerTime(), is(expectedOutputDate));
}
Here is the relevant method in the class that is under test:
fun init(zone: ParkingMeter, plateNumber: String, extendedVariableRateMaxDayMinutes: Int, repository: ParkSmarterRepository) {
this.selectedZone = zone
this.plateNumber = plateNumber
this.extendedVariableRateMaxDayMinutes = extendedVariableRateMaxDayMinutes
if (selectedZone.maxTime < extendedVariableRateMaxDayMinutes * 24 * 60) {
//extendedVariableRateMaxDayJump = selectedZone.maxTime!
}
repository.getCurrentSessions { list, message ->
if (list?.size!! > 0) {
list.forEach {
if (it.zoneID == selectedZone.ZoneId && it.vehicle == plateNumber) {
isNewSession = false
var startDate = it.startTime
var endDate = it.endTime
} else {
isNewSession = true
}
}
}
setCurrentCustomerTime()
}
}
The callback function from retrofit:
fun getCurrentSessions(callback: (currentSessions: List<CurrentSession>?, message: String?) -> Unit) {
val call = ParkSmarter.apiInterface.getCurrentSessions()
call.enqueue(object : Callback<CurrentSessionsResponse> {
override fun onResponse(call: Call<CurrentSessionsResponse>, responseParkSmarter: Response<CurrentSessionsResponse>?) {
try {
callback(responseParkSmarter?.body()?.parkingSessions, responseParkSmarter?.body()?.psResponse?.Message)
} catch (ex: Exception) {
ex.printStackTrace()
}
}
override fun onFailure(call: Call<CurrentSessionsResponse>, t: Throwable?) {
callback(null, "Network Error")
}
})
}
and the retrofit call
#Headers(HEADERS)
#GET("/api/ParkingSession")
Call<CurrentSessionsResponse> getCurrentSessions();