<<<<<<< HEAD <<<<<<< HEAD
=======
Initial commit
- Why Jetpack Compose
- What is Jetpack Compose
- Thinking in Jetpack Compose
- Recomposition
- Thinking in React
- Applying React in Compose
- Differences
- Conclusions
- Reduce maintenance efforts.
- Minimize the complexity and cost of change.
- Enhance usability and extensibility.
- Improve application testability and code readability.
Current Android dev is with the MVVM architecture, which was also very prevalant in pre-react FE architectures including IOS.
- Model
- Data of the application, can't talk directly to a view
- View
- UI of the application, observes data from the View Model
- Model View
- Link between the View and the model, also offers two way data bindings ..
This is a great archichecture why change? From a React perspective it very much comes down to Reasonability: being able to understand and reason over your code!, xml, class models, data streams, it's just far to complex to grok! and then also cohesion over coupling. But first what do want from software.
We want to create CLEAN, Testable usable/extensible and understandable code.
- Reduce maintenance efforts.
- Minimize the complexity and cost of change.
- Enhance usability and extensibility.
- Improve application testability and code readability.
- Coupling - is the dependency amoung units in different modules and reflects the ways in which part of one module influence parts of other modules
- Cohesion - is instead the relationship between the units within a module and indicates how wel grouped the units in the module are
- Maintainable Software - we should aim for low coupling and high cohesion
- Coupling creates brittle highly implicit couplings.
- XML View - Kotlin ViewModel , if you remove one or the other this can result in NullReferences
- Hard to test as a unit
-
We increase Cohesion and move closer to Clean code.
-
Mix Logic with UI?
- it's already mixed but with coupling...
- React came to exactly the same conclusion
-
Everything is in one place, makes it easier to reason over.
-
Easier to test
- A modern Toolkit for building native Android UI
- Declarative programming model
- You declare the layout/ look and feel
- As State Changes your UI automatically updates
- Built on Kotlin
- Tightly integrated into Android Studio
- Composition over inheritence
- Encapsulation
- Recomposition
Jetpack Compose is a modern toolkit for building native Android UI. It's based on the declarative programming model, so you can simply describe what your UI should look like, and Compose takes care of the rest—as app state changes, your UI automatically updates. Because it’s built on Kotlin, it's fully interoperable with the Java programming language and has direct access to all of the Android and Jetpack APIs. It’s compatible with the existing UI toolkit, so you can mix and match classic and new views, and it’s designed with Material and animations from the start.
- Jetpack launched at Googles 2018 I/0 Developer conference
- Jetpack Compose launched at Googles 2019 I/0 Developer conference
- Alphas appeared in 2020 now on 1.0.0-alpha09
- Lead React Native Architect at Air BnB
- Creator of Enzyme
- Hugely popular in the react community, conferences, speaker
- Switched to Google at the same shortly afer Air BnB ditched React native
- Gave a "what if" presentation on the possibility of using Kotlin or Swift with Reacts Paradigm
- Main driving force behind Jetpack Compose and the main link back to the React community
- Declarative API that allows you to render your app UI without Imperatively mutating UI views 1. Imperative way of updating UI is to directly mutate the UI widgets internal state, using methods like addChild or setText, much like in JQuery 2. Becomes messy and hard to track changes over time.
- Simplifies building and updating user interfaces
- Recomposes the entire screen from scratch, applying state changes to the UI 1. Can be expensive although the algorithm for Recomposition is fast.
- Fed data and emits UI elements
- annotated with @Composable
- Wraps your data in other composable like Text.
- Doesn’t return anything, emits UI hierarchy by calling other composable functions.
- Idempotent, Pure, free of side effects, behaves the same way when called a number of times.
-
- Data
- Imperative object orientated UI toolkits, have a tree of internal stateful widgets with getters and setters, which your app logic interacts with.
- With compose, Composables are relatively stateless and are not exposed as objects, but functions.
- You call these with different arguments and they respond with those arguments transformed into UI.
- You update the UI by calling the same composable function with different arguments.
- Data trickles down from the top level composable.
-
- Events
- When the user interacts with the UI through an event ( onClick ).
- The app logic will mutate the state
- This new state is then trickled down by calling the composable with fresh arguments.
- This is known as recomposition.
- Composables are written in Kotlin not XML.
- This means you have the full flexibility of the kotlin language, so you can use all of it's constructs in your composable. This is very similar to React in that it brings you back to the code (JS) instead of having to learn a framework DSL to be able to apply logic your view ...
- In an Imperative UI to change a widget you call a setter on a widget to change its internal state.
- In compose you call a composable function with new data.
- Doing so causes the function to be RECOMPOSED, it is REDRAWN, if necessary with new data.
- The Compose framework can intelligently recompose only the components that changed.
- Composable functions can execute in any order
- Composable functions can execute in parallel
- Recomposition skips as many composable functions and lambdas as possible
- Recomposition is optimistic and may be canceled
- A composable function might be run quite frequently, as often as every frame of an animation!
- Composable functions can execute in any order
@Composable
fun ButtonRow() {
MyFancyNavigation {
StartScreen()
MiddleScreen()
EndScreen()
}
}
- Composable aren’t run in order.
- Compose has the option of recognising that some UI elements are higher priority than others, drawing them first.
- One composable can’t be dependent on a data change of another. They must be self contained and in this situation the data must be central ( even global ) for those composable.
- Composable functions can execute in parallel
@Composable
@Deprecated("Example with bug")
fun ListWithBug(myList: List<String>) {
var items = 0
Row(horizontalArrangement = Arrangement.SpaceBetween) {
Column {
for (item in myList) {
Text("Item: $item")
items++ // Avoid! Side-effect of the column recomposing.
}
}
Text("Count: $items”)
}
}
- Compose can optimise recomposition by running composable functions in parallel. They may execute within a pool of background threads.
- To ensure consistent behaviour all composable should have no side effects. Instead side effects that triggered through callbacks should be run on the UI thread.
- Code that modifies a local variable is not code safe. As the composable could be called multiple times.
- Recomposition skips as many composable functions and lambdas as possible
- Recomposition will only re-compose those elements that have new data/arguments
- Recomposition is optimistic and may be canceled
- Recomposition starts whenever Compose thinks that a parameter has changed.
- It’s optimistic in that it expects to finish recomposition before the parameters change again.
- If a param does change before recomposition finishes, it will discard the last changes and then restart with the new parameter.
- To avoid this ensure that all Composable are idempotent and side effect free
- A composable function might be run quite frequently, as often as every frame of an animation!
- A Composable could run every frame of a UI animation.
- Jank can be experienced if the composable performs an expensive operation.
- MOVE EXPENSIVE WORK TO ANOTHER THREAD, outside of composition and pass the data to Compose using mutableStateOf.
- Start with a mock, UI and Data
- Break UI in a component Hierarchy
- Build a static version in React
- Identify The minimal Representation Of UI State
- Identify where the state should live ( Data down )
- An Inverse Data Flow, top level component passes callbacks to child components to mutate the state.
How do we apply TIR to compose, can we follow the same approach as we would for a normal react appliction? We'll see using the FlickrBrowser app, to walk us through the TIR points and looking at we can apply these.
-
Start with a mock, UI and Data
- Flickr Browser app
-
Break UI in a component Hierarchy
- Container
- SearchBar
- Loading spinner
- Photo List
- Photo List Item
- Photo details
-
Build a static version in Compose!
-
Identify The minimal Representation Of UI State
- Flickr photo list
- Flickr photo item
- Search bar
- loading
- Search Text
-
Identify where the state should live ( Data down )
- Move all the state to the root Activity or Container
-
An Inverse Data Flow, top level component passes callbacks to child components to mutate the state.
- onHandleSearchTextChange - mutates the search text
- useMutableStateOf
- Can be inlined or extrapolated to a view model
- List of flickr photos based on api
{
"photos": {
"page": 1,
"pages": 8864,
"perpage": 1,
"total": "8864",
"photo": [
{
"id": "50718869598",
"owner": "157354161@N02",
"secret": "b2f2c029d8",
"server": "65535",
"farm": 66,
"title": "A7403526-Edit",
"ispublic": 1,
"isfriend": 0,
"isfamily": 0,
"url_s": "https:\/\/live.staticflickr.com\/65535\/50718869598_b2f2c029d8_m.jpg",
"height_s": 240,
"width_s": 240,
"url_o": "https:\/\/live.staticflickr.com\/65535\/50718869598_af9ed208b1_o.jpg",
"height_o": 3250,
"width_o": 3250,
"url_t": "https:\/\/live.staticflickr.com\/65535\/50718869598_b2f2c029d8_t.jpg",
"height_t": 100,
"width_t": 100,
"url_m": "https:\/\/live.staticflickr.com\/65535\/50718869598_b2f2c029d8.jpg",
"height_m": 500,
"width_m": 500,
"url_l": "https:\/\/live.staticflickr.com\/65535\/50718869598_b2f2c029d8_b.jpg",
"height_l": 1024,
"width_l": 1024
}
]
},
"stat": "ok"
}
- Need to add state for the Search Bar
{
"loading": true,
"searchText": "",
}
- State
- Loading
- Text
- Search Text
- Preview for the UI
- debounced search text
- loading spinner when loading
- Run in simulator
- Write a test for the Search Bar
- Show example of themeing
- Obvious ones
- Kotlin
- Composable instead of Component
- Multithreaded!!
- Returns Unit, React returns a composition of components, Compose emits the composition.
- Love it!
- React wrapped in Kotlin clothing
- Theming
- Slow Build in comparison to React Native
- Android Studion - theyve really taken head of developers
- Preview
- Test
- Tooling integration
React | Compose |
---|---|
Reconciler | Composer |
children ( component props ) | children: @Composable () -> Unit |
hooks | @Composable functions, camel case name |
useState | remember {mutableStateOf(0)} |
useMemo | remember |
useEffect | onCommit |
Effect return : () => { /* clean up */ } | onDispose |
On prop change : [prop1, prop2] | onCommit(prop1, prop2) |
On mount useEffect(()=>{}, []) | onActive |
useContext | Ambient |
Provider | Provider |
StyledComponents - Flex | Themeing/ Modifier |
Key | Key composable |
.map | For each |
Types - Unions | Types - Unions |
Render | Composition |
Storybook | Preview |
React | Compose |
---|---|
Components | |
const MyButton = ({ text, icon }) => (
<Row>
{text}
{icon}
</Row>
); |
@Composable
fun MyButton(text: @Composable () -> Unit,
icon: @Composable () -> Unit) {
Row {
text()
icon()
}
} |
State - useState | |
const useTodoItems = () => {
const [items, setItems] = useState([]);
return [items, setItems];
}; |
@Composable
fun todoItems() : List<TodoItem>{
var todoItems by remember { mutableStateOf(listOf<TodoItem>()) }
return todoItems
} |
State - useState | |
const useTodoItems = () => {
const [items, setItems] = useState([]);
return [items, setItems];
}; |
@Composable
fun todoItems() : List<TodoItem>{
var todoItems by remember { mutableStateOf(listOf<TodoItem>()) }
return todoItems
} |
Props / Children | |
const Container = ({ children }) =>
<View>{children}</View>;
const MyButton = ({ text, icon }) => (
<Row>
{text}
{icon}
</Row>
); |
@Composable
fun Container(children: @Composable () -> Unit) {
children()
}
@Composable
fun MyButton(text: @Composable () -> Unit,
icon: @Composable () -> Unit) {
Row {
text()
icon()
}
} |
useEffect - dependencies, properties | |
useEffect(() => {
someEffect(a, b);
}, [a, b]); |
val (searchText, onSearchTextChange) = remember { mutableStateOf("") }
val (todoList, onTodoListChange) = remember { mutableStateOf(List<TodoItem>) }
onCommit(searchText) {
viewModelScope.launch {
val list = api.search(searchText)
onTodoListChange(list)
}
} |
useEffect - run on every render | |
useEffect(() => {
runEveryCompose();
});
|
onCommit({
runEveryCompose()
}) |
useEffect - cleanUp | |
useEffect(() => {
someEffect(a, b);
return () => cleanUp(a, b);
}, [a, b]); |
onCommit(a, b) {
someEffect(a, b)
onDispose { cleanUp(a, b) }
} |
useEffect - once | |
useEffect(() => {
doOnce();
}, []); |
onActive {
doOnce()
} |
StoryBook / Preview | |
|
@Preview
@Composable
fun TodoPreview() {
Todo(
id = "1", task = "I'm in Preview"
)
} |
Context / Provider | |
<TodosContext.Provider value={todoState}>
<TodoActivityScreen>
{children}
</TodoActivityScreen>
</TodosContext.Provider>
const { addTodo, todos } = useTextContext(); |
Providers(AmbientTodos provides TodoState()) {
TodoActivityScreen()
}
val todoViewModel = AmbientTodos.current;
TodoScreen(
items = todoViewModel.todoItems
onAddItem = todoViewModel::addItem
) |
useContext | |
const TodosContext = React.createContext<ITodosState>
(initialState);
export const useTodosContext = () => useContext(TodosContext); |
val AmbientTodos = ambientOf<TodoState>()
val todoViewModel = AmbientTodos.current; |
useMemo | |
const memoizedVal = useMemo(() => expensiveCompute(a, b), [a,b]); |
val memoizedVal = remember(a, b{
expensiveCompute(a, b)
} |
Key | |
const Todo = ({ id, task }) =>
<Text key={id}>{task}</Text>; |
@Composable
fun Todo(id: String, task: String) {
key(id) { Text(task) }
} |
<<<<<<< HEAD
Fixed first commit ======= =======
Cadec 2021 presentation on Jetpack Compose
Initial commit Initial commit