https://kotlinlang.org logo
#compose
Title
# compose
i

ianrumac

10/27/2019, 12:07 PM
Hey people, trying to get image loading from network to work: So I have a
Copy code
Container{
val state = +model{ NetworkImagemodel(bitmap: null); } // this is a @Model annotated data class

glideBitmapReady(src,context) {
 state.bitmap = it //this is callback from Glide's onResourceReady
}
onCommit(state) {
 println("Loaded)"
}
}
I get a
IllegalStateException: Not in a frame
pointing to the change of state model (
state.bitmap = it //this is callback from Glide's onResourceReady
)
a

Andrey Kulikov

10/27/2019, 12:11 PM
could you also show your glideBitmapReady code please?
i

ianrumac

10/27/2019, 12:13 PM
Copy code
Glide.with( context).asBitmap().load(src)
                    .addListener(object :
                        RequestListener<Bitmap> {
                        override fun onLoadFailed(
                            e: GlideException?,
                            model: Any?,
                            target: Target<Bitmap>?,
                            isFirstResource: Boolean
                        ): Boolean {
                            e!!.printStackTrace()
                            return false
                        }

                        override fun onResourceReady(
                            resource: Bitmap?,
                            _model: Any?,
                            target: Target<Bitmap>?,
                            dataSource: DataSource?,
                            isFirstResource: Boolean
                        ): Boolean {
                            callback(resource)
                            return true
                        }
                    }).preload()
I’m an idiot, first thing I should do is switch .submit to .preload() I figure 🤦
okay yeah, found my problem - moved to preload and moved the state check code out of onCommit, got it working now
is there a way to invoke a lambda only on initial render, not on subsequent updates? or should i have to worry about that in the parent of the component?
l

Leland Richardson [G]

10/27/2019, 2:57 PM
Hey @ianrumac it’s really cool to see people trying out this code
there’s a couple of things to consider here
the
glideBitmapReady
function, as you noticed, will run too often
what we can do is use a couple of primitives that compose has to run only when it needs to
for example, the
onCommit
effect is a way to run code when compose actually applies the changes to the UI, and only when some number of inputs have changed (like memo)
i rewrote your glide function a little bit
Copy code
fun glideBitmapReady(src: String) = effectOf<Bitmap?> {
  val result = +state<Bitmap> { null }
  val context = +ambient(AmbientContext)
  +onPreCommit(src) {
    val listener = object : RequestListener<Bitmap> {
      override fun onLoadFailed(
          e: GlideException?,
          model: Any?,
          target: Target<Bitmap>?,
          isFirstResource: Boolean
      ): Boolean {
          e!!.printStackTrace()
          return false
      }

      override fun onResourceReady(
          resource: Bitmap?,
          _model: Any?,
          target: Target<Bitmap>?,
          dataSource: DataSource?,
          isFirstResource: Boolean
      ): Boolean {
          result.value = resource
          return true
      }
    }
    val glide = Glide
    .with(context)
    .asBitmap()
    .load(src)
    .addListener(listener)
    .preload()
    onDispose {
      glide.removeListener(listener)
    }
  }
  result.value
}
this turns it into an “effect”, which is a concept that is going away in compose, but the change will only make things simpler. In the future, you would do the same thing, except the
= effectOf<Bitmap?>
would just turn into
: Bitmap?
and all of the pluses would go away
but to explain the code a little bit, what i’m doing is localizing the state to the effect, so that it can be used more succinctly. I’m also only calling
onPreCommit
when the
src
string changes. this means that things will properly update if you change the src string
i don’t remember all of the glide APIs off-hand, so i guessed that
removeListener
was an API that exists, but let me know if it doesnt
it’s important that I call
onDispose
here with the
removeListener
call, which ensures that as the src string changes, I clean up any old requests that had been set up from earlier versions
from the composable function you wanted to use this in, you would use this like:
var bitmap = +glideBitmapReady(src)
OH. almost forgot. the error you were getting with
IllegalStateException: Not in a frame
was because you were mutating the model in a different thread. This is totally allowed, but you have to do a little bit of extra work. In this case, you need to open up a model transaction and commit it. I’m not in a place to look up the exact API, but I believe if you look in
androidx.compose.frames
it will be something like
Copy code
open {
  myModel.property = newValue
  commit()
}
i

ianrumac

10/27/2019, 3:14 PM
ahhhh thanks for the explanations! noticed the `effectOf`in the docs but wasn’t quite sure what it does. understanding it better now, thanks 👍
l

Leland Richardson [G]

10/27/2019, 3:16 PM
we haven’t been explaining it too thoroughly because it will be going away soon…
i

ianrumac

10/27/2019, 3:18 PM
and thanks for the
open
tip, I assumed that I was out of the UI scope/thread when getting the exception, and started going through `Frames’ to figure out what was going on but couldn’t really wrap my head around it, now it’s more obvious.
l

Leland Richardson [G]

10/27/2019, 3:19 PM
yeah that error message isn’t particularly helpful. we should add some more to it for sure
and also the naming isn’t great. we are renaming “frame” in this context to “transaction” but haven’t yet
i

ianrumac

10/27/2019, 3:22 PM
really? I find frame to have more sense in the context of a UI engine, transaction seems way more generic
l

Leland Richardson [G]

10/27/2019, 3:22 PM
and to be clear, the API of effectOf is going away, but that doesn’t mean you shouldn’t use it. It’s “going away” as in the code you write will be almost identical, you will just be able to remove some words and plus signs…. so definitely use
effectOf
for now
the problem is that
frame
doesn’t actually correspond to the UI’s sense of
frame
here
i

ianrumac

10/27/2019, 3:23 PM
I assume it’s the recorded ‘state’ before a render is invoked?
l

Leland Richardson [G]

10/27/2019, 3:23 PM
no it’s a bit different from that
model objects are locally consistent, in that you can mutate them on one thread without another thread seeing those mutations… until you “commit” those changes.
on the UI thread, you don’t need to commit anything
since it’s also the thread that coordination is done on
the sort of “view” or “scope” of those mutations can be thought of as the frame (in its current usage)
you can open a new frame, make changes, abort them, no one will ever see those changes
you can also open a new frame, make changes, and then commit
and those changes will then get reflected on the main thread
so it starts to feel a lot like a transaction IMO
i

ianrumac

10/27/2019, 3:28 PM
ah makes sense, I thought of frame’s more as a single state ‘log’
esp when thinking about it as being changes done on a
Model
, transaction does fit better
thanks for the quick engine runthrough, you saved me hours of digging through sources
l

Leland Richardson [G]

10/27/2019, 3:32 PM
👍 hopefully our documentation game will improve before an alpha or beta so that people like you don’t lose this time
i

ianrumac

10/27/2019, 3:49 PM
I think just a architecture diagram would be enough for this phase, just so there’s an overview of things
8 Views