On the left is the list displayed in API level 19 and on the right is API 28. For API 19 I am unable to load flag Images using Picasso. https://i.stack.imgur.com/PU7QJ.png
This is how I am doing it right now.
interface ImageFetcher {
fun loadImage(url: String, callback: (bitmap: Bitmap) -> Unit)
}
class RemoteImageFetcher(val context: Context) : ImageFetcher {
override fun loadImage(url: String, callback: (bitmap: Bitmap) -> Unit) {
val target = object : com.squareup.picasso.Target {
override fun onBitmapLoaded(bitmap: Bitmap, from: Picasso.LoadedFrom?) {
callback.invoke(bitmap)
}
override fun onPrepareLoad(placeHolderDrawable: Drawable?) {}
override fun onBitmapFailed(e: Exception?, errorDrawable: Drawable?) {}
}
Picasso.get().load(url).into(target)
}
Related
I need to download multiple images, and after all downloads are completed (outside of the Main Thread), perform other actions in the activity.
I am currently using Glide to download as follows:
ImageDownloader.kt
class ImageDownloader {
fun downloadPack(context: Context, path: String, pack: PackModel) {
for (image: ImageModel in pack.images) {
Glide.with(context)
.asBitmap()
.load(image.imageFileUrl)
.listener(object : RequestListener<Bitmap> {
override fun onLoadFailed(e: GlideException?, model: Any?, target: Target<Bitmap>?, isFirstResource: Boolean): Boolean {
return false
}
override fun onResourceReady(bitmap: Bitmap?, model: Any?, target: Target<Bitmap>?, dataSource: DataSource?, isFirstResource: Boolean): Boolean {
saveImage(path, pack.id, image.imageFileName, bitmap!!)
return true
}
}).submit()
}
}
private fun saveImage(path: String, id: String, fileName: String, bitmap: Bitmap) {
val dir = File(path + id)
if (!dir.exists()) dir.mkdirs()
val file = File(dir, fileName)
val out = FileOutputStream(file)
bitmap.compress(Bitmap.CompressFormat.PNG, 75, out)
out.flush()
out.close()
// to check the download progress for each image in logcat
println("done: $fileName")
}
}
In the activity I call this method inside a CoroutineScope as follows:
PackActivity.kt
class PackActivity: AppCompatActivity() {
private lateinit var bind: ActivityPackBinding
private lateinit var path: String
private lateinit var pack: PackModel
// other basic codes
override fun onCreate(savedInstanceState: Bundle?) {
// other basic codes
path = "$filesDir/images_asset/"
pack = intent.getParcelableExtra(PACK_DATA)!!
bind.buttonDownload.setOnClickListener {
downloadPack()
}
}
private fun downloadPack() {
CoroutineScope(Dispatchers.IO).launch {
val async = async {
ImageDownloader().downloadPack(applicationContext, path, pack)
}
val result = async.await()
withContext(Dispatchers.Main) {
result.apply {
println("finished")
// other things todo
}
}
}
}
}
I am trying to proceed with other actions after downloading all the images in PackActivity.kt, but as a result, using println("finished") and checking the logcat, the code is starting even before the first download starts...
Some information:
PackModel and ImageModel are my data class, where PackModel has the Id of each pack and a list of ImageModel, which in turn has the ImageFileName and ImageFileUrl. All data is obtained from a web request.
I want the images to be saved in the folder data/data/AppPackageName/files/images_asset/PackID/... And with the tests I did, I was unable to use DownloadManager directing the images to this internal folder of the App, that's why I'm using Glide.
The first, crucial step is to adapt the async Glide request into a suspend fun using suspendCancellableCoroutine. Here's how:
private suspend fun downloadBitmap(
context: Context,
image: ImageModel
) = suspendCancellableCoroutine<Bitmap> { cont ->
Glide.with(context)
.asBitmap()
.load(image.imageFileUrl)
.listener(object : RequestListener<Bitmap> {
override fun onLoadFailed(
e: GlideException, model: Any?,
target: Target<Bitmap>?, isFirstResource: Boolean
): Boolean {
cont.resumeWith(Result.failure(e))
return false
}
override fun onResourceReady(
bitmap: Bitmap, model: Any?, target: Target<Bitmap>?,
dataSource: DataSource?, isFirstResource: Boolean
): Boolean {
cont.resumeWith(Result.success(bitmap))
return false
}
}).submit()
}
With that done, now you'll have an easy time of making concurrent downloads and awaiting on all of them:
class ImageDownloader {
suspend fun downloadPack(context: Context, path: String, pack: PackModel) {
coroutineScope {
for (image: ImageModel in pack.images) {
launch {
val bitmap = downloadBitmap(context, image)
saveImage(path, pack.id, image.imageFileName, bitmap)
}
}
}
}
private suspend fun saveImage(
path: String, id: String, imageFileName: String, bitmap: Bitmap
) {
withContext(IO) {
// your code
}
}
}
Watch carefully which dispatchers I use above: using the Main dispatcher for everything except saveImage, which is the only place that contains code that is actually blocking.
Finally, to use everything, this is all you need:
private fun downloadPack() {
GlobalScope.launch {
ImageDownloader().downloadPack(applicationContext, path, pack)
println("finished")
// other things todo
}
}
Again, everything on the Main dispatcher because the blocking code is safely corralled into the IO dispatcher.
I use GlobalScope above for the lack of knowledge of your larger context, but it's probably a bad idea. Writing CoroutineScope(IO).launch has all the same problems, plus allocating several more objects.
Think twice about what's going to happen to your downloads if the user navigates away from the app, or if they navigate away and back repeatedly, triggering a growing pile of background downloads. In the code above, i didn't treat cancellation within suspendCancellableCoroutine because I'm not that intimate with Glide. You should add a cont.onCancellation handler to be correct.
I am trying to achieve paging in my app using android paging library.But I am stuck in one place. The dataSource is not getting created from the factory. Snippet below.
private fun getLivePagedListBuilder(queryString: String): LivePagedListBuilder<Int, News> {
val dataSourceFactory = object : DataSource.Factory<Int, News>() {
override fun create(): DataSource<Int, News> {
return NewsDataSource(queryString)
}
}
return LivePagedListBuilder(dataSourceFactory, config)
}
1. The create method is not getting called. So the return inside tht method not firing.
My DataSource
class NewsDataSource(val searchQuery: String) : PageKeyedDataSource<Int, News>() {
override fun loadInitial(params: LoadInitialParams<Int>, callback: LoadInitialCallback<Int, News>) {
api.searchNews(searchQuery, Constants.perPageLimit, 1)
.enqueue(object : Callback<NewsResponse> {
override fun onFailure(call: Call<NewsResponse>, t: Throwable) {
Log.d("TAG1", "Failure")
}
override fun onResponse(call: Call<NewsResponse>, response: Response<NewsResponse>) {
callback.onResult(response.body()?.news, response.body()?.page - 1, response.body()?.page + 1)
}
})
}
}
The API returns the page number and I am planning to load the next page when the scroll ends
What determines the datatype of the Key in DataSource.Factory<Int, News>()
I am really stuck on this :(
I have an activity that calls a class and runs a function in that class like so:
for (i in 0 until questions) {
droll.droolCalls(sign)
}
This can sometimes run forever as it has to generate a bunch of random numbers, so I want it to be able to run in the background. I wrote an AsyncTask that looks like this:
class MyAsync(
private val droll: CreateLayout,
private val questions: Int, private val sign:Int,
var progressBar: ProgressBar,
var layoutProgress: LinearLayout,
var layoutMain: LinearLayout
) :
AsyncTask<String, Int, CreateLayout>() {
var progress = 0
override fun doInBackground(vararg params: String?): CreateLayout {
for (i in 0 until questions) {
droll.droolCalls(sign)
}
return droll
}
override fun onProgressUpdate(vararg values: Int?) {
super.onProgressUpdate(*values)
progressBar.incrementProgressBy(progress * 10)
}
override fun onPostExecute(result: CreateLayout?) {
super.onPostExecute(result)
layoutProgress.visibility = View.GONE
layoutMain.visibility = View.VISIBLE
}
}
However, when i call the drollclass
MyAsync(droll, totalQuestions,sign, progressBar, Loading, mainLayout).execute("hello")
i get an error from other functions that require drool to run.
this one for example insert_point.addView(droll.makeTextView(numberOfQuestions - 1)) gives me a java.lang.IndexOutOfBoundsException: Index: 2, Size: 0 error because insert_point not getting the data from droll because the Async didn't run? however if i take it out of the Async the for loop our of the Async it works fine.
the whole structure looks something like this
class mainclass{
MyAsync(droll, totalQuestions,sign, progressBar, Loading, mainLayout).execute("hello")
insert_point.addView(droll.makeTextView(numberOfQuestions - 1))
class MyAsync(
private val droll: CreateLayout,
private val questions: Int, private val sign:Int,
var progressBar: ProgressBar,
var layoutProgress: LinearLayout,
var layoutMain: LinearLayout
) :
AsyncTask<String, Int, CreateLayout>() {
var progress = 0
override fun doInBackground(vararg params: String?): CreateLayout {
for (i in 0 until questions) {
droll.droolCalls(sign)
}
return droll
}
override fun onProgressUpdate(vararg values: Int?) {
super.onProgressUpdate(*values)
progressBar.incrementProgressBy(progress * 10)
}
override fun onPostExecute(result: CreateLayout?) {
super.onPostExecute(result)
layoutProgress.visibility = View.GONE
layoutMain.visibility = View.VISIBLE
}
}
}
package www.binexmining.co.`in`.binexmining.binexmining.uam.view.activity
import android.app.Activity
import android.os.AsyncTask
import android.os.Bundle
import android.support.annotation.MainThread
import kotlinx.android.synthetic.main.row_referraluser.view.*
import www.binexmining.co.`in`.binexmining.R
class AsynTaskKotlinMain : Activity()
{
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_landing)
}
override fun onResume() {
super.onResume()
MyAssyn().execute("Pre-Executing Param Put here...","Param For Doinbackgroun")
}
}
class MyAssyn : AsyncTask<Any, Any, Any>()
{
override fun onPreExecute() {
super.onPreExecute()
}
override fun doInBackground(vararg params: Any?) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun onPostExecute(result: Any?) {
super.onPostExecute(result)
}
}
I cannot, for the life of me, instantiate an interface outside of a fragment in Kotlin or Kotlin for Android. It was standard procedure in Java to say something like:
MyInterface mInterfaceListener = new MyInterface(this);
mInterfaceListener.invokeSomeGenericMethod();
Note that mInterfaceListener is referring to an Interface, not an onCLickListener or anything like that
How are interfaces instantiated in Kotlin? How do I make a "listener" and trigger an interface's functions?
Below are some attempts in a very simple app I am doing for learning purposes. Notice the variable mPresenterListener which is an Interface
class QuoteActivity : QuoteContract.ViewOps, AppCompatActivity() {
private lateinit var vText: TextView
private lateinit var vFab: FloatingActionButton
private lateinit var mPresenterListener: QuoteContract.PresenterOperations
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mPresenterListener = this.mPresenterListener
vText=findViewById(R.id.main_quote)
vFab=findViewById(R.id.main_fab)
vFab.setOnClickListener{
mPresenterListener.onQuoteRequested()
}
}
override fun displayQuote(quote: String) {
vText.text = quote
}
}
And my presenter:
class QuotePresenter(private val viewListener: QuoteContract.ViewOps): QuoteContract.PresenterOperations {
private lateinit var modelListener: QuoteContract.ModelOperations
init {
modelListener = this.modelListener
}
override fun onQuoteRequested() {
modelListener.generateQuote()
}
override fun onQuoteGenerated(quote: String) {
viewListener.displayQuote(quote)
}
}
The interface:
interface QuoteContract {
//Methods available to Presenter (Presenter -> View)
interface ViewOps{
fun displayQuote(quote: String)
}
//Ops offered from presenter to view (Presenter->View)
interface PresenterOperations {
//Presenter->View
fun onQuoteRequested()
//Presenter->Model
fun onQuoteGenerated(quote: String)
}
//Ops offered from Model to Presenter (Model -> Presenter)
interface ModelOperations {
fun generateQuote()
}
}
You can do watchers/listeners like this:
val textView: TextView = this.findViewById(R.id.amountEdit)
val watcher = object : TextWatcher {
override fun afterTextChanged(p0: Editable?) {
val inputAmount = textView.text.toString
val amount = if (!inputAmount.isEmpty()) inputAmount.toDouble() else 0.0
conversionViewModel?.convert(amount)
}
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
println("before text changed called..")
}
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
println("text changed..")
}
}
and then:
textView.addTextChangedListener(watcher)
Notice the object in declaring the watcher. Can do the same with a listener.
Also we can use listener without some interface and with default value like:
val someButtonListener: (isChecked: Boolean) -> Unit = {_ -> }
val someButtonListener: (v: View) -> Unit = {_ -> }
I am trying to write a parcelable data object to pass to from activityA to activityB in my android application.
My object is passing with all the data, except my arraylist of the class Available Service
data class AvailableService(val id: Int,
val name: String,
val description: String,
val price: Double,
val currency: String,
val imageUrl: String) : Parcelable {
companion object {
#JvmField #Suppress("unused")
val CREATOR = createParcel { AvailableService(it) }
}
protected constructor(parcelIn: Parcel) : this(parcelIn.readInt(),
parcelIn.readString(),
parcelIn.readString(),
parcelIn.readDouble(),
parcelIn.readString(),
parcelIn.readString())
override fun writeToParcel(dest: Parcel?, flags: Int) {
dest?.writeInt(id)
dest?.writeString(name)
dest?.writeString(description)
dest?.writeDouble(price)
dest?.writeString(currency)
dest?.writeString(imageUrl)
}
override fun describeContents() = 0
}
above is the available serviceClass, next I have Trip, which holds an arraylist of AvailableService.. I observed this in debug, it is successfully writing the arraylist, for some reason I have an issue with reading the data.
data class Trip(val id: String,
val status: String,
val orderedServices: ArrayList<OrderedService>) : Parcelable {
companion object {
#JvmField #Suppress("unused")
val CREATOR = createParcel { Trip(it) }
}
protected constructor(parcelIn: Parcel) : this(parcelIn.readString(),
parcelIn.readString(),
arrayListOf<OrderedService>().apply {
parcelIn.readArrayList(OrderedService::class.java.classLoader)
}
)
override fun writeToParcel(dest: Parcel?, flags: Int) {
dest?.writeString(id)
dest?.writeString(status)
dest?.writeList(orderedServices)
}
override fun describeContents() = 0
}
in case someone wonders what's the fun of the CREATOR does, code below:
inline fun <reified T : Parcelable> createParcel(
crossinline createFromParcel: (Parcel) -> T?): Parcelable.Creator<T> =
object : Parcelable.Creator<T> {
override fun createFromParcel(source: Parcel): T? = createFromParcel(source)
override fun newArray(size: Int): Array<out T?> = arrayOfNulls(size)
}
again, writing succeeds, but reading fails, I get an empty arraylist..
I think that part is the faulty one:
arrayListOf<OrderedService>().apply {
parcelIn.readArrayList(OrderedService::class.java.classLoader)
}
is there a different way to read/write arraylist? am I writing it wrong? reading it wrong?
thanks in advance for any help!
Replace:
arrayListOf<OrderedService>().apply {
parcelIn.readArrayList(OrderedService::class.java.classLoader)
}
with:
arrayListOf<OrderedService>().apply {
parcelIn.readList(this, OrderedService::class.java.classLoader)
}
Change
arrayListOf<OrderedService>().apply {
parcelIn.readArrayList(OrderedService::class.java.classLoader)
}
to
source.createTypedArrayList(OrderedService.CREATOR)
Change dest?.writeList(orderedServices)
to dest?.writeTypedList(orderedServices)
The creator method
companion object {
#JvmField
val CREATOR: Parcelable.Creator<Trip> = object : Parcelable.Creator<Trip> {
override fun createFromParcel(source: Parcel): Trip = Trip(source)
override fun newArray(size: Int): Array<Trip?> = arrayOfNulls(size)
}
}
For API 29 and above
Replace:
arrayListOf<OrderedService>().apply {
parcelIn.readArrayList(OrderedService::class.java.classLoader)
}
with
if (Build.VERSION.SDK_INT >= 29)
parcel.readParcelableList(this, OrderedService::class.java.classLoader)
else
parcel.readList(this as List<OrderedService>, OrderedService::class.java.classLoader)
}
and
override fun writeToParcel(dest: Parcel?, flags: Int) {
dest?.writeString(id)
dest?.writeString(status)
dest?.writeList(orderedServices)
}
with
override fun writeToParcel(dest: Parcel?, flags: Int) {
dest?.writeString(id)
dest?.writeString(status)
if (Build.VERSION.SDK_INT >= 29) {
dest?.writeParcelableList(orderedServices,flags)
} else {
dest?.writeList(orderedServices as List<OrderedService>)
}
}
If Parent and Child model have parcelable then used below one
Parent Model
Parcelable {
constructor(parcel: Parcel) : this(
parcel.createTypedArrayList(ImagesModel.CREATOR)
)
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeTypedList(images)
}