I have an existing view that extends from ConstraintLayout which looks something like this:
class LandingTemplate: ConstraintLayout {
init {
inflate(context, R.layout.landing_template, this)
// Currently this 'recyclerView' is a kotlin synthetic
recyclerView.run {
// this sets up the recycler view
}
}
I'm familiar with view binding with activities and fragments, but I can't find any documentation around the extends layout case.
My question is, what do I replace that initial inflate call with here?
I'm assuming you have a context available from your constructor and your XML layout's top level tag is <merge>. You can use your binding class's inflate to create and add the child layout.
And since this can all be set up in the constructor, you don't need lateinit var like in the Activity/Fragment examples, and can just use val instead.
class LandingTemplate(context: Context, attrs: AttributeSet): ConstraintLayout(context, attrs) {
private val binding = LandingTemplateBinding.inflate(LayoutInflater.from(context), this)
init {
binding.recyclerView.run {
// this sets up the recycler view
}
}
}
you can get layout inflater like below
val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
val view = inflater.inflate(R.layout.landing_temple,this,true)
and you must have valid view construct too
LandingTemple(Context) // for creating view programmatically
LandingTemple(Context,AttrributeSet) // to inflate view from xml , and
//the constructor context is one that you use to call `getSystemService
for more information check
Related
I know it's possible to use a custom view instead of a basic map marker in Google Maps doing what's described in this answer. I was curious if it was possible to achieve a similar affect with Compose?
Since ComposeView is a final class, couldn't extend that directly so I was thinking of having a FrameLayout that could add it as a child. Although this seems to be causing a race condition since composables draw slightly differently from normal android Views.
class MapMarkerView : FrameLayout {
constructor(context: Context) : super(context) {
initView()
}
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
initView()
}
private fun initView() {
val composeView = ComposeView(context).apply {
layoutParams = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)
}
composeView.setContent {
// this runs asynchronously making the the bitmap generation not include composable at runtime?
Text(text = "2345", modifier = Modifier.background(Color.Green, RoundedCornerShape(4.dp)))
}
addView(composeView)
}
}
Most articles I have read have some callback function for generating a bitmap like this which wouldn't necessarily work in this use case where each view is generated dynamically and needed to be converted to a bitmap right away for the map.
The MapMarkerView you are attempting to create is an indirect child of ViewGroup. ComposeView is also an indirect child of ViewGroup which means you can directly substitute a ComposeView in places where you intend to use this class. The ComposeView class is not meant to be subclassed. The only thing you really need to be concerned with as a developer is its setContent method.
val composeView: View = ComposeView(context).apply{
setContent {
Text(text = "2345", modifier = Modifier.background(Color.Green, RoundedCornerShape(4.dp)))
}
}
Now you can replace the MapMarkerView with this composeView in your code or even use it at any location where a View or any of it's subclasses is required.
I have a compound view that I want to create its viewmodel by ViewModelLazy, I need to send the ViewModelStoreOwner of the view to ViewModelLazy but trying to get the ViewModelStoreOwner using ViewTreeViewModelStoreOwner.get(this) always returns null. The compound view itself is a simple view, but I am using it in a recyclerview adapter that resides in a fragment. Right now, I am getting forced to use the parent fragment ViewModelStoreOwner, which is causing all the items in the adapter to have the same viewmodel instance. I searched for an example on how to use ViewTreeViewModelStoreOwner but I can't find one, am I missing something?
Note: I am injecting the viewmodel by dagger-hilt
This example shows how it can be implemented in custom view.
class SummaryView(context: Context, attrs: AttributeSet?) : ConstraintLayout(context, attrs) {
private val viewModel by lazy {
ViewModelProvider(findViewTreeViewModelStoreOwner()!!).get<SummaryViewModel>()
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
viewModel.summaryModel.observe(findViewTreeLifecycleOwner()!!, ::populateSummaryView)
}
private fun populateSummaryView(summaryModel: SummaryModel) {
// do stuff
}
}
Found this great example here
1, you need update
androidx.activity to 1.2.0
androidx.fragment to 1.3.0
2, activity or fragment set its ViewModelStore to rootViw via setTag, so any view in activity or fragment's rootView gets the same ViewModelStoreOwner and also same ViewModelStore.
// ComponentActivity line 409
ViewTreeViewModelStoreOwner.set(getWindow().getDecorView(), this)
// ViewTreeViewModelStoreOwner line 50
view.setTag(R.id.view_tree_view_model_store_owner, viewModelStoreOwner);
I'm trying to use Robinhood Spark in my app but my entire UI is built using jetpack compose. I'm going through these docs but it's only mentioned how you can use XML resource files like String, Dimensions, Colors, Images, Vector drawbles, Icons, and Fonts. I don't know which one Spark falls under. Looking at the XML code it's a LinearLayout but I can't see how that falls under any of the resource types I've mentioned prior. So for now I'm going through non-compose documentations to see if there is some kind of class or method I can use but would appreciate some feedback and help.
It is as nglauber mentioned, but you would need to define layout parameters that will fit into your compose view as well.
More about it https://foso.github.io/Jetpack-Compose-Playground/viewinterop/androidview/
AndroidView(factory = { ctx ->
val view = LayoutInflater.from(ctx)
.inflate(R.layout.activity_sample, null, false)
.apply {
layoutParams = LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT)
}
// Use your view as usual...
view
}, update = {
// here update your view or get element from it and update
})
You can use AndroidView to load your XML file.
AndroidView(
factory = { context: Context ->
val view = LayoutInflater.from(context)
.inflate(R.layout.your_layout, null, false)
// Use your view as usual...
view // return the view
},
update = { view ->
// Update view if needed
}
)
I am looking for utility or any way how can I measure the time required for inflater to inflate some portion (view or viewgroup or whole screen) in Android.
I know there is default feature in debug mode that shows overdraw, but I need to have measure the time taken to inflate view.
What is the best way to do this.
Thx.
Check out lucasr/probe. Here is a blog post by a Facebook engineer on improving view measure time.
Hierarchy Viewer is also a useful visual tool to see which part of your layout can be improved.
In 2019, you can use android x benchmark library easy for benchmark inflate time.
android doc write-benchmark
#RunWith(AndroidJUnit4::class)
class ViewBenchmark {
#get:Rule
val benchmarkRule = BenchmarkRule()
#Test
fun simpleViewInflate() {
val context = ApplicationProvider.getApplicationContext()
val inflater = LayoutInflater.from(context)
val root = FrameLayout(context)
benchmarkRule.keepRunning {
inflater.inflate(R.layout.test_simple_view, root, false)
}
}
}
or you can use system trace to benchmark only inflate layout code
In 2022, you have androidx.benchmark library where you can measure layout inflation time. Check out the sample code here from Google. https://github.com/android/performance-samples
ViewInflateBenchmark.kt
#RunWith(AndroidJUnit4::class)
class ViewInflateBenchmark {
#get:Rule
val benchmarkRule = BenchmarkRule()
#Test
fun benchmarkViewInflate() {
val context = InstrumentationRegistry.getInstrumentation().context
val inflater = LayoutInflater.from(context)
val root = FrameLayout(context)
benchmarkRule.measureRepeated {
val inflated = inflater.inflate(R.layout.item_card, root, false)
}
}
}
I have a generic custom view like
class MyGenericCustomView<T>(context: Context, attrs: AttributeSet) : AnotherView(context, attrs) {
...
}
In the Activity/Fragment XML I have:
<package.name.MyGenericCustomView
android:id="#+id/custom_id"
....
/>
If I use the old way, I can get my "typed" custom view using something like:
override fun onCreate(...) {
...
val myCustomView = findViewById<MyGenericCustomView<String>>(R.id.custom_id)
...
}
But if I use the Android Kotlin Extension (synthetic) to have an object named with the same ID, I dont have a way to pass the Generic type, so
//custom_id is of type MyGenericCustomView<*>
One solution is to create a specific class like
class MySpecificCustomView(context: Context, attrs: AttributeSet) : MyGenericCustomView<String>(context, attrs) {
....
}
But I dont want to create this boilerplate class.
Is there any solution to specify a custom type using Kotlin Extensions only?
Thanks
Since you cannot specify the type parameter in the XML and type parameters are also erased in the byte code, you could simply cast the value to the proper generic type MyGenericCustomView<String>.
So something like that should work:
val myView = custom_id as MyGenericCustomView<String>
For better usage in your Activity/Fragment I would personally use lazy { } like this:
class MyActivity() : Activity(…) {
val myView by lazy { custom_id as MyGenericCustomView<String> }
...
}