Subtyping Composables - TypeAlias Show Clip

  Рет қаралды 3,090

Dave Leeds

Dave Leeds

Күн бұрын

Пікірлер: 40
@typealias
@typealias Ай бұрын
Typically, I've just embraced the openness of composables, but Paco's question piqued my interest. I'd love to hear your thoughts on the advantages and disadvantages of the approach. What risks would this introduce?
@janpeters6776
@janpeters6776 Ай бұрын
Dave, your videos are pure gold - your way of explaining things is just so easy to follow even for what are complex topics to me still. I am still learning, and "a lot to learn I still have", but for instance I always thought that compose was purely android related, and I learned a lot of new things today. There are just not enough hours in a day to dig into everything, but I for sure am trying and your videos contribute to that a lot and motivate - keep up the great work! Thank you!
@typealias
@typealias Ай бұрын
Hey, thanks so much, Jan! Yes, there's so much to learn even just between Android and Kotlin, but I'm really glad these videos have been helpful for you to keep up with it! Thanks for your kind words!
@darthnelly13
@darthnelly13 Ай бұрын
I think it'd be more clear (and less error prone, as you found) if you extended an interface with a single composable invoke method, instead of extending the lambda signature. That way: 1. the compose compiler can generate the overloads on the interface, because the composable annotation is already on there 2. it's easier to understand at a glance for someone coming into the project Just because people didn't know you could extend a lambda and it's cool that you have the freedom to do it, doesn't mean you should
@typealias
@typealias Ай бұрын
Hey, thanks for commenting! Yeah, actually - my first stab at this was to use an interface like `interface ToolbarButton : @Composable () -> Unit`, but this caused the same runtime exception as the abstract class approach, which is how I ended up on the concrete classes. By changing to `interface ToolbarButton { @Composable operator fun invoke() }`, though, it does work, so I'd prefer that, for sure. 🎉 And, of course - "unexpected" and "cool" aren't quality attributes that matter in most software projects, which is why I didn't present the solution as a recommendation, but as a call for feedback.
@HoussamElbadissi
@HoussamElbadissi Ай бұрын
W-wait a sec... you could extend function types just like that? I mean it makes sense, but it's pretty dope to see it in action! If I had to this before watching this video I would be creating a whole (fun) interface to wrap a single function for me :p This livestream clip format is quite nice, hope to see more!
@typealias
@typealias Ай бұрын
Hey, thanks Houssam! Yeah, I also tend to prefer those fun interfaces whenever I can, but pretty neat to be able to use a class if you need state to go along with it! Glad to hear you like the clip format! Thought I'd try it out. =)
@pavlosoia
@pavlosoia Ай бұрын
There is an excellent book called "Secure by Design" I believe this approach reduces the number of bugs during development.
@blaupunkt1221
@blaupunkt1221 Ай бұрын
I'm learning a lot from your channel. Keep up the good work!
@pablovaldes6022
@pablovaldes6022 Ай бұрын
😮 wow this blew my mind. Can you actually make a class extends from a function? Without declaring a typealias or anything. Astonishing 🎉 In regards to the issue it seems like a compose compiler plugin. It should have error out before runtime. Saying you can't invoke a function passing an abstract class as parameter
@typealias
@typealias Ай бұрын
Yeah, pretty neat how that works! And yes, I don't know much about the compose compiler plugin, but it seems to be looking for a concrete implementation of that invoke() function in order to create the required overload (with Composer, int parameters). Not sure if it _has_ to be that way or if it's just a side-effect of the way it's implemented. But an IDE warning for that would definitely be helpful.
@ArthurKhazbs
@ArthurKhazbs Ай бұрын
Classes directly implementing function types as interfaces, huh… It totally makes sense, but somehow I never thought about it!
@typealias
@typealias Ай бұрын
Yeah, interesting how we can include an `operator fun invoke()` on any class to get that invokable syntax, but if we also want to be able pass the object itself as a function, we have the option to implement a function type, too. I don't think I've often directly implemented function types in production code, but once in a while it's just what you're looking for.
@AnkitGusai
@AnkitGusai Ай бұрын
Pretty cool, thank you.
@abdushakoor0099
@abdushakoor0099 Ай бұрын
Wow that's interesting. Coming from flutter to compose, missed this but glad that it's possible.
@kamalsharma8547
@kamalsharma8547 Ай бұрын
Hi.. I was planning to switch to flutter due to availability of more jobs... is there any specific reason for moving to compose from flutter ?
@abdushakoor0099
@abdushakoor0099 Ай бұрын
@kamalsharma8547 Hey Kamal sharma, the only reason one could switch from flutter to native if there's some native stuff to be handled that is difficult to handle in flutter. For this specific app, I switched because I had to use track location of drivers in background with a foreground service. For apps that don't require rhis kinda native code, flutter is still an amazing option.
@kamalsharma8547
@kamalsharma8547 Ай бұрын
​@@abdushakoor0099thanks!.. could you tell me where I can find best resources for learning flutter?
@abdushakoor0099
@abdushakoor0099 Ай бұрын
@@kamalsharma8547 official docs are the best resources to get the basics right. Flutter Mapp is also a good source on KZbin.
@pavel_pospi
@pavel_pospi Ай бұрын
Thank for the video! From my perspective, there are still many questions. For example, very often you need to add default parameters - but these default parameters might be custom getters on @Composable scope. How would you create such component classes? Keep in mind the code below might be improved a lot. Other question might be around stability and recompositions of such class instances. @Composable public fun Button( text: String, modifier: Modifier = Modifier, // Good practice to have default Modifier contentColor: Color = LocalContentColor.current, // Custom @Composable getter ) { TextButton(onClick = { TODO() }) { Text( text = text, color = contentColor, ) } }
@typealias
@typealias Ай бұрын
Hey, thanks so much, Pavel! Good questions, for sure! I tried out shifting those into the constructor, but it's not possible to annotate a constructor with `@Composable` - so it works with `Modifier`, but not with `LocalContentColor`. But fear not! With a companion object that has a factory function named `invoke()`, we can still achieve the desired effect! 😂 (Before I show this, I'd like to clarify to everyone that this is absurd and I'm not in any way recommending this approach! I'm just amused that it works!) ``` class ToolbarButton private constructor( private val text: String, private val modifier: Modifier, private val contentColor: Color ) : @Composable () -> Unit { @Composable override fun invoke() { Button(onClick = { println(text) }, modifier = modifier) { Text(text, color = contentColor) } } companion object { @Composable operator fun invoke( text: String, modifier: Modifier = Modifier, contentColor: Color = LocalContentColor.current ): ToolbarButton { return ToolbarButton(text, modifier, contentColor) } } } ``` Anyway, your point about referencing default arguments is a great one, and I don't see any _reasonable_ way to account for those. Not to mention, instantiating a class on each recomposition is likely bad for performance. Or at least, we'd have to measure it to know. Thanks again for your thoughts! 👍
@pavel_pospi
@pavel_pospi Ай бұрын
@@typealias Interesting solution, thank you very much for you response.
@nsshurtz
@nsshurtz Ай бұрын
abstract class Something: @Composable () -> Unit { @Composable abstract override operator fun invoke() } does this solve the problem? I don't think it needs to be concrete, you just need a way of marking it as @Composable every step along the way.
@typealias
@typealias Ай бұрын
Yes, you're right - thanks! It can be abstract, but the `@Composable` isn't automatically applied from the supertype. This does also work with an `interface Something` - as long as we redeclare the invoke() function. Thanks, Nathan!
@VivartPandey
@VivartPandey Ай бұрын
I don’t see any benefit here, slot pattern is make it more open but here we are restricted to only ToolbarButton. Why not just use toolbarText in Toolbar composable?
@reosfire
@reosfire Ай бұрын
Inheritance of annotated functional type.. This is first time when i see such thing. I have no idea how compose compiler ate this one :)
@rosse6899
@rosse6899 Ай бұрын
You need to test that you've not broken recomposition. Try passing in a value to your class constructor that would change (eg updated in the view model). I'd be surprised if recomposition would work here. Will try it out and let you know
@bobnich
@bobnich Ай бұрын
Have you tried? I did and also checked the layout inspector to look at the number of recompositions for these composables. It works just like plain composables. Here is the code: @Composable fun TestScreen( modifier: Modifier = Modifier ) { var state by remember { mutableStateOf("before") } Column( modifier = modifier ) { FirstButton(state)() SecondButton( onClick = { state = "after" } )() } } interface Button { @Composable operator fun invoke() } class FirstButton( private val text: String ) : Button { @Composable override fun invoke() { Text( text = text ) } } class SecondButton( private val onClick: () -> Unit, ) : Button { @Composable override fun invoke() { androidx.compose.material3.Button( onClick = onClick ) { Text("test button") } } }
@typealias
@typealias Ай бұрын
Yes, let me know how it goes for you! When I tested it out here, it did work with remembers and recomps. I'd have at least some performance concerns about instantiating new objects on each recomp, but I suppose that'd need to be measured to know for sure - and a lot of it would depend on the nature of the app (e.g., if the UI requires a lot of scrolling or is mostly static).
@Z1ew-k1q
@Z1ew-k1q Ай бұрын
I am curious how you are able to play the kotlin syntax as a trump small toy , you always create sometime new
@andrermn
@andrermn Ай бұрын
what's the difference to just passing in the text as before instead of the button callback?
@QuantuMGriD
@QuantuMGriD Ай бұрын
Guess u got it wrong. The button1 and button2 slots are meant for passing button composables but there is nothing stopping us from passing literally any composable. So in order to constraint this we introduce classes which provides the concept of types. Now passing in anything other than that specific type will result in a compile time error.
@QuantuMGriD
@QuantuMGriD Ай бұрын
Nice!, did know we could do this. But It really feels pointless to use this as one of the main ideas of compose is to make it functional as such we can get rid of complex class hierarchies that the view system introduced and Trying to introduce type constraints in compose just makes us write more boilerplate and doesn't seem to provide much gains
@typealias
@typealias Ай бұрын
Yeah, I think I agree. I'm not convinced a slot API is quite a good fit for Compose. The risk of passing an incorrect UI element isn't the same as the risk of passing an incorrect object type to a function in a dynamic language (i.e., the usual benefits of class hierarchies). The UI will at least still render, no matter what composable you use, but passing a number where a string is expected (in a dynamic language) could easily crash the app, depending on how it's used. So I'm not sure the type safety is worth the boilerplate here.
@stantoniification
@stantoniification Ай бұрын
Even if this is possible, I think it is worth seriously thinking whether or not you should do this. - I think the team behind Compose recommend against it.
@inamortz2372
@inamortz2372 Ай бұрын
The massive red flag for me is mixing object-oriented design (subtyping) with Compose's functional approach
@typealias
@typealias Ай бұрын
Hey, thanks for commenting, Steven! Yes, in fact, my whole objective here is to get feedback about whether it's a viable approach. Since it's off-the-rails from what I've typically seen, I'm wondering not just whether the Compose team would recommend against it, but if so, specifically why - what are the risks, etc.?
@typealias
@typealias Ай бұрын
@inamortz2372 - Yeah, this approach definitely goes against the grain, so it would need to have a big enough payoff to make it worthwhile
Applying the State Pattern in Kotlin
12:55
Dave Leeds
Рет қаралды 7 М.
Inline Functions: inline, crossinline, and noinline
11:59
Dave Leeds
Рет қаралды 7 М.
Что-что Мурсдей говорит? 💭 #симбочка #симба #мурсдей
00:19
coco在求救? #小丑 #天使 #shorts
00:29
好人小丑
Рет қаралды 120 МЛН
How Strong Is Tape?
00:24
Stokes Twins
Рет қаралды 96 МЛН
小丑女COCO的审判。#天使 #小丑 #超人不会飞
00:53
超人不会飞
Рет қаралды 16 МЛН
The Essence of Coroutines
8:10
Dave Leeds
Рет қаралды 13 М.
Immutable and Persistent Lists - TypeAlias Show Clip
12:08
Dave Leeds
Рет қаралды 2,1 М.
Coroutines: Concurrency in Kotlin
30:22
Dave Leeds
Рет қаралды 19 М.
Applying the Strategy Pattern in Kotlin
10:20
Dave Leeds
Рет қаралды 9 М.
Anonymous Functions Aren't Lambdas
9:08
Dave Leeds
Рет қаралды 4,1 М.
What's new in Flutter 3.27 & Dart 3.6
17:51
SpeedKodi
Рет қаралды 2,1 М.
Что-что Мурсдей говорит? 💭 #симбочка #симба #мурсдей
00:19