Fetch GIF File in Fresco With Kotlin - android

I have a recyclerview that displays a list of images in the GIF format using Fresco, a library for Android.
When the user clicks on an image in the recyclerview, I need to be able to fetch the .gif file and store it as somename.gif
I tried this code:
val contentRequest = ImageRequest.fromUri(items[position])
val cacheKey = DefaultCacheKeyFactory.getInstance().getEncodedCacheKey(contentRequest, null)
val imageResource = ImagePipelineFactory.getInstance().mainFileCache.getResource(cacheKey)
val file = (imageResource as FileBinaryResource).file
Log.d("VED-APP", file.name)
But the result ends up being: Lw0g1Jq7J0jUxSCCEZe3UwRa6-0.cnt, which is different from "somename.gif".
So, I tried this code instead:
val contentRequest = ImageRequest.fromUri(items[position])
val imagePipeline = Fresco.getImagePipeline()
val dataSource : DataSource<CloseableReference<CloseableImage>> =
imagePipeline.fetchDecodedImage(contentRequest, null)
But, the problem is, I need to write the object dataSource to a file.
I found this code sample that could help me write dataSource to a file, but I don't know how to convert it into Kotlin. Could someone help me out in converting this?
Or, if anyone knows of a better way to fetch files from Fresco, could they suggest an alternative method?
DataSource<CloseableReference<T>> dataSource = ...;
DataSubscriber<CloseableReference<T>> dataSubscriber =
new BaseDataSubscriber<CloseableReference<T>>() {
#Override
protected void onNewResultImpl(
DataSource<CloseableReference<T>> dataSource) {
if (!dataSource.isFinished()) {
// if we are not interested in the intermediate images,
// we can just return here.
return;
}
CloseableReference<T> ref = dataSource.getResult();
if (ref != null) {
try {
// do something with the result
T result = ref.get();
...
} finally {
CloseableReference.closeSafely(ref);
}
}
}
#Override
protected void onFailureImpl(DataSource<CloseableReference<T>> dataSource) {
Throwable t = dataSource.getFailureCause();
// handle failure
}
};
dataSource.subscribe(dataSubscriber, executor);
Important Update One -
I figured out how to convert most of the above code to Kotlin. The only problem is that I don't know how to use the appropriate executor to subscribe to the dataSubscriber, see the code below for clarification.
According to the documentation, I should be using a background thread executor, but I'm not sure how to do that in Kotlin.
val dataSubscriber : DataSubscriber<CloseableReference<CloseableImage>> =
object : BaseDataSubscriber<CloseableReference<CloseableImage>>() {
override fun onNewResultImpl(dataSource: DataSource<CloseableReference<CloseableImage>>?) {
if(!dataSource!!.isFinished) {
return
}
val ref : CloseableReference<CloseableImage>? = dataSource.result
if(ref != null) {
try {
val result = ref.get()
} finally {
CloseableReference.closeSafely(ref)
}
}
}
override fun onFailureImpl(dataSource: DataSource<CloseableReference<CloseableImage>>?) {
Log.d("VED-APP","Fresco Failed to Fetch?")
}
}
val contentRequest = ImageRequest.fromUri(items[position])
val imagePipeline = Fresco.getImagePipeline()
val dataSource : DataSource<CloseableReference<CloseableImage>> =
imagePipeline.fetchDecodedImage(contentRequest, null)
dataSource.subscribe(dataSubscriber,/*I don't know what to put here */)

Related

How to test ViewModel + Flow

I'm doing a small project to learn flow and the latest Android features, and I'm currently facing the viewModel's testing, which I don't know if I'm performing correctly. can you help me with it?
Currently, I am using a use case to call the repository which calls a remote data source that gets from an API service a list of strings.
I have created a State to control the values in the view model:
data class StringItemsState(
val isLoading: Boolean = false,
val items: List<String> = emptyList(),
val error: String = ""
)
and the flow:
private val stringItemsState = StringtemsState()
private val _stateFlow = MutableStateFlow(stringItemsState)
val stateFlow = _stateFlow.asStateFlow()
and finally the method that performs all the logic in the viewModel:
fun fetchStringItems() {
try {
_stateFlow.value = stringItemsState.copy(isLoading = true)
viewModelScope.launch(Dispatchers.IO) {
val result = getStringItemsUseCase.execute()
if (result.isEmpty()) {
_stateFlow.value = stringItemsState
} else {
_stateFlow.value = stringItemsState.copy(items = result)
}
}
} catch (e: Exception) {
e.localizedMessage?.let {
_stateFlow.value = stringItemsState.copy(error = it)
}
}
}
I am trying to perform the test following the What / Where / Then pattern, but the result is always an empty list and the assert verification always fails:
private val stringItems = listOf<String>("A", "B", "C")
#Test
fun `get string items - not empty`() = runBlocking {
// What
coEvery {
useCase.execute()
} returns stringItems
// Where
viewModel.fetchStringItems()
// Then
assert(viewModel.stateFlow.value.items == stringItems)
coVerify(exactly = 1) { viewModel.fetchStringItems() }
}
Can someone help me and tell me if I am doing it correctly? Thanks.

How do I implement googleMap.snapshot() in kotlin?

I have this function in my MainActivity.kt file which I want to produce a screenshot of the google map being rendered, then display it in an image view and save it. This is related to what I am trying to do but I don't have enough experience with Kotlin, Java and android studio to understand what isn't working.
My main issue is that I can't figure out how to get / access a variable containing the Bitmap
This is being added to the code from This Google Tutorial
private fun takePicture(googleMap: GoogleMap) {
var bitmapfrommap: Bitmap? = null
val snapshotReadyCallback : GoogleMap.SnapshotReadyCallback = GoogleMap.SnapshotReadyCallback {
fun onSnapshotReady(snapshot: Bitmap) {
bitmapfrommap = snapshot
imageView.setImageBitmap(bitmapfrommap)
var filename = "export.png"
var path = getExternalFilesDir(null)
var fileOut = File(path, filename)
if (bitmapfrommap != null) {
fileOut.writeBitmap(bitmapfrommap!!, Bitmap.CompressFormat.PNG, 85)
}
}
}
val onMapLoadedCallback : GoogleMap.OnMapLoadedCallback = GoogleMap.OnMapLoadedCallback {
googleMap.snapshot(snapshotReadyCallback, bitmapfrommap)
}
googleMap.setOnMapLoadedCallback(onMapLoadedCallback)
}
binding.takeMapScreenshot.setOnClickListener {
myMap?.snapshot { bitmap -> //here you do whatever you want with the resulting bitmap }
}

Proper way of handle sealed class property in kotlin

Hey I am working in Android Kotlin. I am learning this LatestNewsUiState sealed class example from Android doc. I made my own sealed class example. But I am confused little bit, how can I achieved this. Is I am doing right for my scenario or not?
DataState.kt
sealed class DataState {
data class DataFetch(val data: List<Xyz>?) : DataState()
object EmptyOnFetch : DataState()
object ErrorOnFetch : DataState()
}
viewmodel.kt
var dataMutableStateFlow = MutableStateFlow<DataState>(DataState.EmptyOnFetch)
fun fetchData() {
viewModelScope.launch {
val result = repository.getData()
result.handleResult(
onSuccess = { response ->
if (response?.items.isNullOrEmpty()) {
dataMutableStateFlow.value = DataState.EmptyOnFetch
} else {
dataMutableStateFlow.value = DataState.DataFetch(response?.items)
}
},
onError = {
dataMutableStateFlow.value = DataState.ErrorOnFetch
}
)
}
}
fun fetchMoreData() {
viewModelScope.launch {
val result = repository.getData()
result.handleResult(
onSuccess = { response ->
if (response?.items.isNullOrEmpty()) {
dataMutableStateFlow.value = DataState.EmptyOnFetch
} else {
dataMutableStateFlow.value = DataState.DataFetch(response?.items)
}
},
onError = {
dataMutableStateFlow.value = DataState.ErrorOnFetch
}
)
}
}
Activity.kt
lifecycleScope.launchWhenStarted {
viewModel.dataMutableStateFlow.collectLatest { state ->
when (state) {
is DataState.DataFetch -> {
binding.group.visibility = View.VISIBLE
}
DataState.EmptyOnFetch,
DataState.ErrorOnFetch -> {
binding.group.visibility = View.GONE
}
}
}
}
}
I have some points which I want to achieve in the standard ways.
1. When your first initial api call fetchData() if data is not null or empty then we need to show view. If data is empty or null then we need to hide the view. But if api fail then we need to show an error message.
2. When view is visible and view is showing some data. Then we call another api fetchMoreData() and data is empty or null then I don't want to hide view as per code is written above. And If api fails then we show error message.
Thanks in advance

Using nested CoroutineScopes to upload images and keeping track of them

I am a newbie to android coroutines my requirements
Need to upload 20 images
Keep track of upload(at least when it gets finished I need to hide progressBar of each image)
After uploading all the images need to enable a "next" button also
Here is my try:
private fun startUploading(){
// Get AWS data
val accessKey = sharedPreferences.getString(getString(R.string.aws_access_key), "").toString()
val secretKey = sharedPreferences.getString(getString(R.string.aws_secret_key), "").toString()
val bucketName = sharedPreferences.getString(getString(R.string.aws_bucket_name), "").toString()
val region = sharedPreferences.getString(getString(R.string.aws_region), "").toString()
val distributionUrl = sharedPreferences.getString(getString(R.string.aws_distribution_url), "").toString()
var totalImagesNeedToUpload = 0
var totalImagesUploaded = 0
CoroutineScope(Dispatchers.IO).launch {
for (i in allCapturedImages.indices) {
val allImageFiles = allCapturedImages[i].viewItem.ImageFiles
totalImagesNeedToUpload += allImageFiles.size
for (j in allImageFiles.indices) {
CoroutineScope(Dispatchers.IO).launch {
while (true) {
val internetActive = utilsClassInstance.hasInternetConnected()
if (internetActive){
try {
val file = allImageFiles[j]
if (!file.uploaded) {
// Upload the file
val cfUrl = utilsClassInstance.uploadFile(file.imageFile, accessKey, secretKey, bucketName, region, distributionUrl)
// Set the uploaded status to true
file.uploaded = true
file.uploadedUrl = cfUrl
// Increment the count of total uploaded images
totalImagesUploaded += 1
// Upload is done for that particular set image
CoroutineScope(Dispatchers.Main).launch {
mainRecyclerAdapter?.uploadCompleteForViewItemImage(i, j, cfUrl)
// Set the next button enabled
if (totalImagesUploaded == totalImagesNeedToUpload){
binding.btnNext.isEnabled = true
}
}
break
}else{
totalImagesUploaded += 1
break
}
} catch (e: Exception) {
println(e.printStackTrace())
}
}
}
CoroutineScope(Dispatchers.Main).launch {
if (totalImagesUploaded == totalImagesNeedToUpload){
updateProgressForAllImages()
binding.btnNext.isEnabled = true
}
}
}
}
}
}
}
fun uploadFile(file: File, accessKey:String, secretKey:String, bucketName: String, region:String, distributionUrl: String): String{
// Create a S3 client
val s3Client = AmazonS3Client(BasicAWSCredentials(accessKey, secretKey))
s3Client.setRegion(Region.getRegion(region))
// Create a put object
val por = PutObjectRequest(bucketName, file.name, file)
s3Client.putObject(por)
// Override the response headers
val override = ResponseHeaderOverrides()
override.contentType = "image/jpeg"
// Generate the url request
val urlRequest = GeneratePresignedUrlRequest(bucketName, file.name)
urlRequest.responseHeaders = override
// Get the generated url
val url = s3Client.generatePresignedUrl(urlRequest)
return url.toString().replace("https://${bucketName}.s3.amazonaws.com/", distributionUrl)
}
There are total "n" images that I need to upload
every image is getting uploaded in different Coroutine because I need to do the parallel upload
The whole question is how to know that all the images are uploaded and enable a next button?
Your code seems very unstructured. You have an infinite loop checking for network availability. You have a nested loop here to upload images (Why?). You are creating a lot of coroutine scopes and have no control over them
Based on the 3 requirements that you mentioned in the question, you can do something like this:
val imagesToUpload: List<File> = /* ... */
var filesUploaded = 0
lifecycleScope.launchWhenStarted {
coroutineScope { // This will return only when all child coroutines have finished
imagesToUpload.forEach { imageFile ->
launch { // Run every upload in parallel
val url = utilsClassInstance.uploadFile(file.imageFile, ...) // Assuming this is a non-blocking suspend function.
filesUploaded++
// Pass the `url` to your adapter to display the image
binding.progressBar.progress = (filesUploaded * 100) / imagesToUpload.size // Update progress bar
}
}
}
// All images have been uploaded at this point.
binding.btnNext.enabled = true
}
Ideally you should have used a viewModelScope and the upload code should be in a repository, but since you don't seem to have a proper architecture in place, I have used lifecycleScope which you can get inside an Activity or Fragment

Kotlin Coroutines wait for job to complete

I have a ViewModel. Inside it, I have a function which fetches some images from the phones internal storage.
Before the fetching is complete it is exposing livedata in mainactivity. How to make coroutines to wait for the task to complete and expose the live data.
// This is my ViewModel
private val _image = MutableLiveData<ArrayList<File>>()
val images: LiveData<ArrayList<File>> get() = _image
fun fetchImage(){
val file = Path.imageDirectory // returns a directory where images are stored
val files = arrayListOf<File>()
viewModelScope.launch {
withContext(Dispatchers.IO) {
if (file.exists()) {
file.walk().forEach {
if (it.isFile && it.path.endsWith("jpeg")) {
files.add(it)
}
}
}
files.sortByDescending { it.lastModified() } // sort the files to get newest
// ones at top of the list
}
}
_image.postValue(files)
}
Is there any other approach to make this code much faster by any other methods?
Do it like this:
fun fetchImage() = viewModelScope.launch {
val file = Path.imageDirectory // returns a directory where images are stored
val files = arrayListOf<File>()
withContext(Dispatchers.IO) {
if (file.exists()) {
file.walk().forEach {
if (it.isFile && it.path.endsWith("jpeg")) {
files.add(it)
}
}
}
files.sortByDescending { it.lastModified() } // sort the files to get newest
// ones at top of the list
}
_image.value = files
}

Categories

Resources