Declarative UI patterns (Google I/O'19)

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments

I was afraid it would just be another Anko layouts but not at all, it's a shift paradigm to UI systems like React and Flutter. I am super HYPED!

As I am playing around with React and Kotlin/JS in a Kotlin Multiplatform project I learned this pattern recently and it does feel right.

You don't reference views but let's say an editText changes so onChange you set that text into a State object which you then use when you need that value. It makes somewhat the UI code clearer.

I wanna see this in the new Motion Layout editor 😍

👍︎︎ 18 👤︎︎ u/agherschon 📅︎︎ May 09 2019 🗫︎ replies

Can someone tell me why they are telling us about making these functions as pure as we can and then they add @Model annotation that uses mutable properties? We already have LiveData and Delegates.observable() in Kotlin.

Why not simply call the component with the new data when it's ready? Why should we bother setting our mostly immutable (in the context of MVI) models to a mutable buffer somewhere in the UI?

👍︎︎ 12 👤︎︎ u/Mishkun 📅︎︎ May 09 2019 🗫︎ replies

They said most of the right things to alleviate my fears. I was skeptical at first, but this looks like it might be pretty amazing.

I've used react before and thought it would lead to some bad application design. Particularly because UI components are suppose to directly control the application state. They explicitly call out that behavior in this video. Yay!

Two thing I still don't fully understand how it works, is how do you replace inheritance. Like how do you create common shareable behaviors for views.

And how do you reference the views to manipulate them later?

👍︎︎ 20 👤︎︎ u/devsquid 📅︎︎ May 09 2019 🗫︎ replies

It's that @GenerateView annotation that makes this really interesting.

👍︎︎ 6 👤︎︎ u/Zhuinden 📅︎︎ May 09 2019 🗫︎ replies

I like the new direction taken by Android SDK guys. Here is some feedback about the presentation.

  • You don't need code generation to call (compose) functions. Why not just take Anvil approach instead of code generation? It is declarative, you can actually compose functions by calling them. Or am I getting something wrong?
  • All these @Composable functions are mutating a singleton. That makes it hard/impossible to parallel them if you want (you mentioned you wanted parallel execution for some reason), and it can also be a source of bugs when you're calling @Composable functions without treating the singleton's lifecycle. I like AnkoContext approach more, when you're passing the mutable state explicitly (though it can be significantly simplified and optimized for the new UI toolkit). A better (from my point of view, especially if you want multi-threading) solution could have methods like this:

        ComposableUI().apply {
            storyWidget(stotyData) // the actual rendering
        }.updateView(rootView)
  • Adding LiveData into the mix, starting asynchronous operations inside UI - it all just breaks everything that was said in the introduction about responsibilities, event directions and having a single source of truth. It looks like the introduction came from Redux world, but the rest is a mix of different reactive patterns that are opposite to having the single source of truth. I'd like new UI toolkit to be more friendly to Redux and single source of truth (single source of data).
  • "because code is data". Was it a buzzword from LISP? In Kotlin code is not data. We do not have macro system or even `eval`.

Overall, I like the new API more than the current.

👍︎︎ 11 👤︎︎ u/konmik-android 📅︎︎ May 09 2019 🗫︎ replies

Some guy here said, that he works on Compose at Google, are you here? Can you say anything about performance? How is it so far, compared to default View system?

👍︎︎ 3 👤︎︎ u/ArmoredPancake 📅︎︎ May 09 2019 🗫︎ replies

YESSSS. REDUCE ALL THE THINGS

👍︎︎ 3 👤︎︎ u/ursusino 📅︎︎ May 09 2019 🗫︎ replies

Yay for Flutter Android Compose!

👍︎︎ 3 👤︎︎ u/MarkOSullivan 📅︎︎ May 09 2019 🗫︎ replies

I'm not sure if I understood correctly, but didn't they recommend to make UI model classes mutable? Does it mean they are still not ready to let the UI hierarchy be touched by multiple threads?

👍︎︎ 2 👤︎︎ u/harold_admin 📅︎︎ May 09 2019 🗫︎ replies
Captions
ROMAIN GUY: Good afternoon, everyone. I'm all Romain Guy, ADAM POWELL: I'm Adam Powell. JIM SPROCH: And I'm Jim. ROMAIN GUY: All right, and we're here to talk about UI patterns. Funny, a lot of you are interested in that suddenly. So there was a stealth title for the talk, so obviously it's about Jetpack Compose on [INAUDIBLE] UI toolkit that we're working on, and that's available in open source. And so we want to talk a little more about this today. But first we want to talk a little bit of history and explaining why we're doing this. So how did we get here? A lot of you obviously are familiar with views and activities and fragment, and I'm sure you love those APIs, they're close to perfect. So just to give you a little bit of background. So this is a diagram that Chad did for a talk that we gave, I think, last year. Shows you a timeline of what we've been up to, especially on the Android Toolkit team. So after we shipped Android 1.0, we didn't do anything for many, many years. And then we finally woke up and decided to write new libraries to help you. So things like Recycler View, Constraint Layouts, Arch Components, and of course, we had the adoption of Kotlin. So we have been working on new things, but one things we one thing we never changed was the UI toolkit itself. We did a lot of work underneath to make it better, we had hardware acceleration, we fixed some bugs, we added some bugs, we added a few features, but we never changed it fundamentally. So one of the things that happened over 10 years ago was this. This is a device that we call the Sooner, and this was the first device that I used to write Android code when I joined Google. So to give you an idea, this device had a single CPU running at 80 megahertz. It didn't have a GPU. We had 64 megs of RAM, and the screen was 320 by 240. This was not the first device that we shipped. We shipped the G1, which was slightly better, but that was similar in spirit. We shipped it with the GPU. We mostly didn't use it. So [INAUDIBLE] toolkit was pretty much design for this device. Fast forward a few years, and now we have this kind of devices. So this is a Pixel 2, I believe, and the screen resolutions now are 2160 by 1080 or 2960 by 4040 on the XL variants. And here you can see a screenshot of the Sooner at the same scale, and it fits comfortably over just one icon on the launcher. So we're still using the same UI toolkit, so maybe it was time to rethink our approach to UIs to better serve those devices. And on top of that, so we build the architecture components over the past couple years, and the approach we took was extremely interesting. We were more open. We did a lot of alphas and betas and RCs. We talked with you, the community, a lot to hear what you wanted from us. I mean, you build the applications. We don't really anymore, so we need your input to know what it is you need, what is you want, what are the problems that you're facing every day. So, yeah, what are you asking for? And what would you like us to do? So when we ask this question for the UI toolkit, in particular, one of the answers we got was unbundle the UI toolkit. Because until now, every time we wanted to ship a fix in the UI toolkit, or every time we went to ship a new feature, we could do this easily ourselves, but then you had to wait for just a few years to be able to raise your main API level. And I know I've had many discussions personally with some of you where I said, I fixed your bug, you're like, yeah, I don't care because the fix is not going to reach my users' devices for a long, long time. So, not helpful at all. So we are thinking about moving some of our widgets to Jetpack, make them be a support library. Let's take text view, move it entirely in Jetpack, and then we'll be able to deliver fixes. But as we were thinking about doing this we thought, well, if we're going to do this, how about we start improving those widgets a little bit. What could we do to improve the APIs? And I'm sure you all love the APIs of our UI toolkit, but as Chad put it, API design is building future regrets. And we have a lot of regrets-- a lot, even more than you do. Here are some examples. So for instance view.java, do you know how big it is? I'm sure you have big files in your source code, I'm pretty sure you don't have files as big as this one. So we have almost 30,000 lines of code in view.java, yeah. [APPLAUSE] That is Google's [? scale ?] right here. To be fair, it's a lot of comments but still this is not very good design. So that's one thing. And then there's random stuff. For instance, Spinner. Why do we have this class called Spinner? It's a combo box or dropdown. We don't like Spinner, nobody does, and a bunch of people ask, why is it called Spinner? I'll show you why. So when we shipped our first SDK, this is what it looked like. So I ran this on my Mac, and it does spin. [LAUGHTER] So we're not entirely crazy or stupid, we just changed our mind. Another example that I really like is Button. Button, if you look at the source code of button.java, it's extremely interesting. There's very few lines of code in there because it extends TextView. Why? It's a good question. Because when you need to display text, the problem is that TextView can do a lot of things. For instance, you can make a button selectable if you want, and I'm pretty sure you can also make it editable. [LAUGHTER] All right and of course, there are tons of other issues but Adam who's going to talk more about those. ADAM POWELL: Yeah, Romain told me that I had to take the slide that started with fragments here. I'm not entirely sure why. So as you stop to factor some of your UIs on Android, and you start to have to make some decisions about how you're going to write your UI components. And this is a question that we've had a lot over the years. Do I write a fragment or do I write a custom view or custom viewgroup? And the problem with this is that the answer can be really different depending on the kind of component that you're writing, and each approach has some of its own challenges. For example, if you see this line of code somewhere because someone decided that they were going to write a custom view to solve a particular problem, what exactly is your first reaction? ROMAIN GUY: Awesome. JIM SPROCH: No, Romain. ADAM POWELL: No? OK, so custom views are kind of daunting to write because you really have to get so many things right, especially if you want to expose a first class API surface. You've got properties that need their getters and setters, pretty standard stuff. You've got XML attributes, which means that you need to start adding in more stuff in your attrs.xml, define styleables. Speaking of stylables, you need to define actual default styles and probably edit some default theme attributes to make sure that all those styles get wired up properly. And that's before you even get to the sheer volume of code that you need for dealing with things like layout or touch events. So meanwhile, fragments give you a whole lot of flexibility, plus all the lifecycle hooks for hanging your application logic from, especially now that you've got arch components to give you some of this in a more pluggable way. But this kind of makes them better suited for coarser grained situations. Reusable hold article displays with interactive subcomponents and not really so much things like single buttons. But the problem with this is that your apps aren't static. They really change and grow over time. So there's a potentially really high engineering cost to having to cross over from one of these abstractions to another as your use cases change. So you insulate yourself from that change. You build other architectures to keep your views, your fragments, and activities as removed from these things as possible, and maybe actually make it a little bit easier to test along the way. No matter which of these architectures, or some of them that aren't listed that you might choose, they all take on the same basic shape. You bind some of this stuff in your activity, your views publish events and receive state, and everything in between is kind of a judgment call. So these architectures really exist to help define and reason about your apps' data flow and answer the three main questions. So what is the source of truth? Who owns it? And more specifically, who can change it? So once you answer some of these questions, then you realize that the Android UI toolkit doesn't really work this way. Views kind of own their own state and make their own changes whether you want them to or not. Most of these view state changes at least have some optional listeners that you can attach so you can respond to some of those changes and do something about it. But really, the Spinner is another great example here, as odd as it is. And it's got this onSelectedItemChanged listener that tells you when the user changed the value. You've picked a new value from the dropdown. And it gives you this callback after the items already changed. And for a lot of UIs, this is kind of useless because then you have to change it back after the fact. So if you've ever created one of those UIs where you have a custom dot, dot, dot, you pop up a dialog, you've let the user make some custom selections, and then you have to change the adapter, set the value to something new. It's kind of a song and dance that's not a lot of fun. And you also have to deal with this idea of these reentrant listener calls once you do set something back. So your listener has to handle both the cases where you meant to change it on purpose or when the user did it. And that just is more and more code that you can get wrong. But there's some advantages to this kind of approach. I mean, like, I would love to just be able to change my salary and then tell Romain all about it after the fact. ROMAIN GUY: We'll talk about it at work. ADAM POWELL: No, this is me telling you after the fact. ROMAIN GUY: Yeah, we'll talk about it. ADAM POWELL: OK. So speaking of events, when you have a single source of truth, you don't have all of these problems. Every piece of state has one owner. And only the owner can make changes to that particular state. So that means that the owner is listening to events that are happening somewhere else in the UI, and it makes changes in response. How many of you have seen some code like this? Hopefully, not too recently-- oh no, we've got a few hands. OK, so Android's UI APIs were really kind of designed for this world-- handling events by implementing several different listener interfaces in one class where that state lives. By implementing these interfaces where the state is, you can make sure that you don't have to lace that state through too many different places. And this is kind of gross, but lambdas weren't even part of the language when this was common practice. So we can take a few liberties here. But if you let somebody who's comfortable with Kotlin lose, they might go nuts and play some golf. They might write something more like this. And the advantages of something like this is that it's event driven and it's built on composing behaviors rather than based on inheritance. You can kind of get away with implementing a bunch of interfaces on a central class like the slide we saw before when you have a small team. If everybody fits in the same room and you can just ask each other questions, you know that you're not going to make a mess in front of one another. But as app and team sizes grew, it became much less sustainable to do that. So by leaning into Kotlin in particular, we can write much more powerful and more composable APIs. But really, whether you've decided to write a fragment or a view, the changes that you have to make really kind of look similar if you're trying to write something that's really reusable, something that exposes a first class API surface for other people to use. You'll have something that looks either MyFragment or MyView, and it might be a Kotlin or a Java file. You have an XML file that holds your layout for that particular file potentially, if it's something complicated, if it's something more than just a simple button that clicks. You've got some stuff in your attrs.sml file that defines the stylable and all the different attributes that it can take. And you also have to make sure that you don't step on that shared namespace that resources work in. Can't declare two attributes for two completely different stylables if they have a different type. That would be silly. And then finally, you have to deal with everything around styling. So you're spreading all of this code that really has to do with the same component across many different files. And by breaking that spatial locality, it makes it harder to regain context or flow at a glance after you've been interrupted. So for example, if you have pets or small children or an open plan office, you might get interrupted pretty often. So really we're trying to address all of these things at once. So first off, we want to make sure that we unbundle from the platform releases so that you can take advantage of what we're working on immediately. We want to make sure that you have fewer technology stack flow charts to traverse when you're deciding how to build your component. We want to make sure that we're not telling you, well, if you're doing something that's bigger like this, start with a fragment, otherwise a button. And if you're not sure, flip a coin, see how it goes, maybe refactor later. We want to help you clarify state ownership and event handling. This is really big because this is one of those things that ends up being almost an afterthought in some applications if you are not thinking about it upfront. But it's really the flow of data through your application that can be the most complex interactions. And finally, we want help you just write less code. So the simplest program that you can write in Kotlin is really, really simple. So whenever you're writing a GUI app though, they're not this kind snapshot in time. It's not a fire and forget construct where you run it once and then the program terminates. You're done, it's done its job. So we kind of need to handle this idea of an event loop and things that change over time. And Kotlin really gives us some huge advantages for working with asynchronous code now. But can we go even further? Can we make it as simple as this to write an Android app? JIM SPROCH: Well, maybe we can. What if instead of calling print line, we called text? What if calling text would admit a text widget to the UI hierarchy? This is the idea behind Jetpack Compose. It's a declarative UI toolkit built for Android. It's inspired by frameworks like React, Litho, Vue.js, and Flutter, but it's written completely in Kotlin and is fully compatible with the existing Android view system. It aims to simplify the way that you write code by making your Android UI hierarchies more declarative. With Compose, your UI is defined as a function, and this function transforms data into view hierarchy. In this case, the input is a simple piece of data, a person's name, and the output is a UI hierarchy, is a text widget. When you need to update the name, we can just re-invoke this function with the new data, and the UI hierarchy will contain a text widget with the new text. This radically simplifies your application because you no longer have to have separate code paths for inflating the initial views and then updating the views later. If we invoke the function with an appropriate name, then we'll get text, hello world, on the screen. When the data changes, we can invoke the function again with the new data and the new data appears with a new greeting on the screen. Each time we invoke the function with different data, the text on the screen is updated. But what exactly is Compose and how does this work? Among other things, Compose is a completely new set of widgets and toolkit APIs built on top of composable functions. They're not views. They're not fragments. They're something smaller, more modular, easier to work with, and easier to test in isolation. Compose is also a Kotlin compiler plugin, which makes it possible to define your own declarative functions using our declarative API. The Compose compiler is what powers the new widget set and makes it easy for you to define your own composable widgets. Compose widgets are also fully compatible with the existing view system. Whether you're working with Kotlin or the Java programming language, we provide ways for you to use the new widget set in your existing view hierarchy, and you can use existing views in the Compose hierarchy. You can mix and match, you can adopt it incrementally and you can adopt it at your own pace, just like Kotlin itself. It's also very early stage. We're still in active development, and we're moving to ALSP so that you guys can get an early preview and so that you can be a part of its development too. So we can collect feedback from you, but it's not ready for production use yet. If you did want to use Compose in an application, it might look something like this. You can call composable functions directly from the content of your activity. All composable functions are annotated with an @Composable annotation. This tells the Composer compiler that your function should be treated as a widget. And it allows us to jump in and perform the necessary rewrites at the right time. We've been working with the JetBrains team to make all of this possible and it's still very experimental. But basically, we intercept calls to composable functions, and that's what allows us to provide the declarative API. Ultimately these functions are fully featured Kotlin functions, which allows us to do some really interesting things. For example, if we wanted to greet the user 10 times, we could use a simple for loop to do exactly that. If we want to iterate over a list of names, we can greet a whole bunch of people all at once. We can also have logic to show something completely different when the list is empty. But the most important thing about composable widgets is that they're composable. Compose makes it trivial to define your own composable functions and reuse them throughout your application. Your UI hierarchy is built by having composable functions call other compatible functions. ADAM POWELL: So our story widget here uses several stock elements that we're going to provide out of the box to build up a pretty straightforward layout, but you might see another issue here if you're not suspending disbelief quite enough looking at our slide wear here. So as we know, if that list right there is really, really big, then this would create a whole lot of these story widgets that we're not going to see. As Android developers, we've been trained over the years to avoid this. In fact, I think about 10 years ago, Romain and I gave our first talk together where we were talking about list view. So this today would be the job of a recycler view, kind of our list view two widget that you're all probably familiar with. But that means that we have to factor out an adapter to handle the recycler view itself requesting and rebinding those views. So as you probably know, there's really no good way to fit a recycler view adapter onto a slide. It's a lot of code even in the best case, and I'm using data binding to cheat a bit here. And several things just aren't shown at all, like the diff callback for helping to handle animations on data set changes. And we certainly don't have anything about event handling in here, but you kind of get the basic idea. So let's come back to this loop in the Jetpack Compose version and see what else we can do. Well, this is Kotlin after all. We can define a generic composable function that looks like a for each loop by using a trailing lambda argument and have it emit each story widget as it's asked for. So what we did here is we just defined our recycler view adapter in one line of code. So the implementation of the scrolling list itself can be just as smart as a recycler view. The body that we pass to a composable function call can itself be a computable function lambda so that scrolling list can choose to call it multiple times or not at all. Much more powerful than just passing parameters through an include tag since it means the scrolling list doesn't need to know anything about what we're going to put in there when we use it. You can put entirely different structures in here just depending on code that you're running and defining. So we can do some other kind of neat things too. We can offer some really powerful hooks into our runtime for lifecycle and subscription management that can work together seamlessly with things like live data, RX Java or the new Kotlin flows. So up until now, we can do most of what we've shown here just with a clever DSL. But here, observe will return a new value each time it changes and cause the news feed function around it to run again. So that'll feed new data to our scrolling list much like a recycler view adapter data swap will. So observe as shown here is just defined in library code. There's nothing special here. There's nothing that we've implemented that's based on internal API. You can write your own, too, if you have your own favorite observable or callback types. So what else does this imply about how the scrolling list must work? So let's go ahead and put it into something a little bit more sophisticated. So we saw with observing a live data that we can trigger a recomposition when something changes. So let's use an async loading function that can give us a placeholder immediately but the result of running some sort of lazy loaded block once it's actually completed. So all of these sorts of complex image loading scenarios that we're all familiar with suddenly become something very simple that we can use anywhere. And also like we saw with the scrolling list, we can weave data across layers of the hierarchy, and they don't have to be direct tree children either. It doesn't matter what kind of chrome or how many layers of hierarchy that card or column might add in between in order to do their work. So we can go further than we can with something like data binding, and we can write a whole lot less wire up code. We can just use the lexical scope of that image or story that we already have, and we can refer to the story headline several layers deep. This is really part of what helps make composable functions composable, how they're kind of getting their name. We can leave this as is. I mean, it's a lot of code for a slide, but it's not a lot to glance at an editor. Or we can just use our normal tools that we've got and highlight extract method on any part of it and go ahead and generalize it. This isn't anything magic. It's just the same tools and techniques that we're already used to doing when we're writing code. So we've talked about how we can observe changes in live data and we've seen how we can show place holders while we request a data load from somewhere else. But what if we get an update to one of our story data objects? So our story data that we use as a parameter in there is really most naturally represented as a Kotlin data class. There's really nothing too special about it. But we'd like to be able to update our UI if anything about it changes. What if this was mutable? So if it were a fragment, we might use an arch components view model with some live data as properties and use the same technique that we saw earlier. But this is going to get really repetitive really quickly, especially with all that extra subscription management, we're kind of deciding where all of this goes. Really we just wanted to write that code from the last slide, and you can. So Composer's compiler plug-in lets you mark a data class as a model which make the properties observable when it's used in a compose function. Now, if our story changes, the story widget that read its fields will know to recompose. The Compose runtime observes the changes and will cause the story widget function to rerun and update our UI with the new data. JIM SPROCH: When you're generalizing your code like that, it's really important to break it up into multiple compatible functions and understand how the data flows through your application. The application passes data-- in this case, a list of stories-- into the news feed. The news feed is then going to iterate over this list of stories, extracting each of the pieces of story data from the list and passing one story data to each story widget. The story widget then reads the title, the image, and the content from the story data. The important thing here is that the parent is always in control of the data for the child. If the data needs to be shared between multiple widgets, the data should be hoisted up to a common ancestor and passed down to each of the widgets that needs it. Children should not be reading from global variables or global data stores. They should be a side effect free and should not do anything except what the parent tells them to do. As your application scales, this architecture makes your code much easier to reason about. But what happens if the application wants to know when a user clicks on a story? One interesting pattern is to pass lambdas down because code is data which allows us to have events propagate upwards. It looks something like this. In this case, the news feed takes an unselected lambda, which is going to take in some story data, which is the current story. It will then, when the news feed invokes the story widget, it's going to take this lambda and create a new lambda of its own and pass that lambda to the story widget. The story widget then passes that lambda to a clickable widget, which will respond to the click. When a user clicks on the story, clicks on the clickable widget, the unclick will fire which will cause the event to propagate back up to the lambda that was created and passed in. This lambda will call unselected, which is the lambda that was passed to the news feed by the application. So the parent is always in control. The data-- in this case, the lambda-- is being passed down, and then the event bubbles back up. If we look at this in diagram form, we see that the application starts with some data, passes it down to the news feed, which passes it down to each of the story widgets, which in turn pass the information down to the composable building blocks that build up each widget. Then when a user clicks on a piece of the widget, that triggers an event which calls the lambdas going back up the hierarchy, back up to the screen. Ultimately the application can then update the applications data model and the new data will be passed back down all the way down until it's displayed onto the screen. Notice that the data was always passed top down and data always comes in as parameters. The widget didn't have to reference the activity. It didn't do anything except code that was controlled by the parent. We'll come back to this with the Spinner example. ADAM POWELL: That's right. So the Spinner has a problem that the code that we just saw doesn't in that the Spinner makes you deal with multiple sources of truth. So our app has a state, the Spinner has a state, and the Spinner doesn't ask us before letting the user change its state. That's the basic shape of the problem. And there's two ways to solve this when you have this, like one of these sides must win if you want to have a single source of truth. So we could design some of these components such that the Spinner itself is the source of truth. And you could see how you would put that together even with the API that we just saw. And it basically looks like having to add a whole bunch of validation APIs. It means that every user interaction widget needs these fully formed APIs for anything that you can change to sort of filter through what you might do in order to validate that what the user's inputting is actually correct. And it scales kind of poorly if you're writing those components yourself. You want to have a case where it's turtles all the way down. You want to make sure that it's as easy to write a component as it is to consume a component, so you kind of don't want everybody to have to deal with writing all their APIs twice, writing these sort of validation filters for every property that you can set. So the other option that we think is better that we're certainly pitching here is to let the app own its own data and have the apps backing data model-- the source of truth that you're using that's part of your data repository be the source of truth instead. So this has a whole lot of other advantages. JIM SPROCH: Let's take a look at a concrete example. Here we have a food preferences widget that we define as a composable function. It takes in some user preference data, and ultimately it will emit a Spinner which has some set of options that are available to the user. Notice that the Spinner's data is set by the selected parameter. So the favorite food of the user is passed directly to the Spinner. The parent widget, in this case food preferences, is in control of what the spinner is showing. And when the spinner has a user indicate that they want to make a change, the user can then execute the unchange code which will result in the data being updated. The Spinner doesn't know which data is being updated or what to do, it just calls on change. When the data does get updated, this Compose function will get recoomposed and the new selected value will be set. We can apply the same approach to text. So in this case, we have an editable text for the user's name, and all that the unchanged handler does is set the name. This looks pretty generic and you might wonder why the editable text couldn't just set the text itself. But maintaining this one-way dataflow allows us to do some really interesting things. For example, if we have a phone number preferences, then when a new potential value comes in, we can do filtering. We can remove any invalid characters, and then we can format the phone number so that it looks nice when the user is typing it in. And then we set the data model to the new formatted phone number. Again, this will trigger a recomposition which will cause the editable text to show the new value. ADAM POWELL: All right, so let's go ahead and go back to some of our goals from the beginning that we listed through. Did we actually meet some of those goals? And I kind of think we did. So in order to meet our unbundling goals, we're open sourcing a new UI API and component set for Jetpack We've offered computable functions that scale across these layers of abstraction. A composable function can do the same sorts of jobs as a fragment or as a custom view group or even as something as small as a single button. We've kind of established this guideline of one-way observable dataflow as a first class concept and we're building the system to make it easy to sort of stay on this happy path. And finally, you have less code and you can keep it in one place. You don't have to split your focus across multiple files just to write a piece of your UI. JIM SPROCH: OK, that was a lot of information in one short talk, and we're only just scratching the surface. Some of you look excited and others might have a lot of questions. For example, what about view compatibility? Will this work with the existing views, and how do I integrate this into my application? So one of the things that you can do is have a annotation that indicates that this code should be usable from within a Android view. And when you do that, it will generate a view class. This class is just a standard view like any other from your code possible function. And you can use it from XML, as shown below, or you can call the find view by ID and get a reference to the node and pass data directly to the view. There's other things that might be interesting to you. For example, we've been giving a lot of thought to how Layout Editor and Preview Tool would work. We've been giving a lot of thought to animation, styles, themes, and constraint layout. All of these things are really important to get right before we go with a stable release. And so we're going to make sure that we have support for everything that you would need to build beautiful applications. And we're still thinking about a lot of other things. We're continuing this exploration. We have ideas around multi-threaded layout, around asynchronous parallel execution of composable functions, around memoization, and much more. All of these things become feasible because of the declarative programming model, and we encourage you to start exploring these ideas with us. We'd love to hear your ideas. ROMAIN GUY: All right just want to make one clarification. You saw an example of Jetpack Composer that was using a Spinner. We don't have a spinner yet, and it be called Spinner. [APPLAUSE] ADAM POWELL: How are people going to understand what to use? ROMAIN GUY: But if you really want one, you can build one, that's the whole point. Yeah, so I just want to reiterate that here, one of the key elements is-- well, two key elements that I really love-- first, composition. If you go look at the source code in AOSP, you can look at how some of the widgets were implemented and you'll see they are that simple. You start to write some code, but you should look at the checkbox, for instance, it's just a few lines of code, and everything it uses is available to you to create your own. The one-way data flow model is also really important because a lot of the bugs and a lot of the [INAUDIBLE] plate in your code comes from trying to keep two data models in sync at all time. And I mess that up all the time, and I'm sure you do as well. So we just got rid of the problem altogether. So Jetpack Composer is not ready to ship yet. We've established that. Please go take a look at the source code, if you go to d.android.com/jetpackcompose, we have explanations. We have a readme that explain how the source tree's architectured. You can run a special version of Android Studio that will let you play with it, compile it. We have a few demos that are available. You can look at the entire source tree. But in the meantime, you can get ready before this becomes available. So one thing you can do is just play with it, but you can also try to structure your application to try to fill this one-way data flow model. So one thing you can do already is try to eliminate all the [INAUDIBLE] you have because every time you do this, it's almost a [INAUDIBLE]. Especially if you called get on the view, that means you're trying to synchronize the data model of the view with the data all of your application. And this is a potential source of bugs. And you won't be able to do this anymore in Jetpack Compose because your functions, your composables, just emit a new tree. At no point can you get a handle on the actual, not physical, but the actual new view object that will be put on screen. So you need to get rid of this habit like basically right now. Also you can start looking at architecture components. How many of you are using the arch components in one form or another? All right, you are all awesome. Everybody who didn't raise their hands, run back to your desk and go fix that please. So you have several things you can use like ViewModel and LiveData, but there are also other solutions out there, so please try to use those. And now we have a few minutes left, so here's the URL that you can check out if you want to know more, if you want to find a source tree. [MUSIC PLAYING]
Info
Channel: Android Developers
Views: 89,098
Rating: 4.9150686 out of 5
Keywords: type: Conference Talk (Full production);, pr_pr: Google I/O, purpose: Educate
Id: VsStyq4Lzxo
Channel Id: undefined
Length: 35min 10sec (2110 seconds)
Published: Wed May 08 2019
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.