Slide 1

Slide 1 text

FROM XML TO COMPOSE MY JOURNEY OF TRANSFORMING AN EXISTING LARGE APP TO JETPACK COMPOSE AHMED TIKIWA | SENIOR SOFTWARE ENGINEER - ANDROID @ LUNO | @AKITIKKX PHOTO BY ILYA PAVLOV ON UNSPLASH

Slide 2

Slide 2 text

WHY COMPOSE?

Slide 3

Slide 3 text

WHAT ARE OTHER COMPANIES SAYING?

Slide 4

Slide 4 text

“It’s much easier to trace through code when it’s all written in the same language [Kotlin] and often the same f ile, rather than jumping back and forth between Kotlin and XML” - Monzo WHAT ARE OTHER COMPANIES SAYING? FROM XML TO COMPOSE, MY JOURNEY OF TRANSFORMING AN EXISTING LARGE APP TO JETPACK COMPOSE - AHMED TIKIWA

Slide 5

Slide 5 text

“Our theming layer is vastly more intuitive and legible. We’ve been able to accomplish within a single Kotlin f ile what otherwise extended across multiple XML f iles that were responsible for attribute de f initions and assignments via multiple layered theme overlays.” - Twitter WHAT ARE OTHER COMPANIES SAYING? FROM XML TO COMPOSE, MY JOURNEY OF TRANSFORMING AN EXISTING LARGE APP TO JETPACK COMPOSE - AHMED TIKIWA

Slide 6

Slide 6 text

WHEN WAS JETPACK COMPOSE INTRODUCED

Slide 7

Slide 7 text

Announced at Google I/O 2019 as a preview “One of the areas we never solved was UI. We really wanted to look at how could you make it super simple to develop UI.” 
 - Karen Ng, Group Product Manager at Google “What I think is once people start seeing Compose in action, it really becomes a delightful thing to program” 
 - Leland Richardson, Software Engineer at Google Excited about new library SOURCE: HTTPS://EVENTS.GOOGLE.COM/IO2019/RECAP/ WHEN WAS JETPACK COMPOSE INTRODUCED FROM XML TO COMPOSE, MY JOURNEY OF TRANSFORMING AN EXISTING LARGE APP TO JETPACK COMPOSE - AHMED TIKIWA

Slide 8

Slide 8 text

LEARNING COMPOSE

Slide 9

Slide 9 text

LEARNING COMPOSE FROM XML TO COMPOSE, MY JOURNEY OF TRANSFORMING AN EXISTING LARGE APP TO JETPACK COMPOSE - AHMED TIKIWA

Slide 10

Slide 10 text

LEARNING COMPOSE FROM XML TO COMPOSE, MY JOURNEY OF TRANSFORMING AN EXISTING LARGE APP TO JETPACK COMPOSE - AHMED TIKIWA

Slide 11

Slide 11 text

LEARNING COMPOSE FROM XML TO COMPOSE, MY JOURNEY OF TRANSFORMING AN EXISTING LARGE APP TO JETPACK COMPOSE - AHMED TIKIWA

Slide 12

Slide 12 text

Created in 2015, available on Google Play Store Open-source, o ff icially part of Google Developers Dev Library Dashboard screen Search screen Explore Screen Show Detail Screen Seasons list Episodes list Trakt account screen SOURCE: HTTPS://EVENTS.GOOGLE.COM/IO2019/RECAP/ ABOUT THE APP FROM XML TO COMPOSE, MY JOURNEY OF TRANSFORMING AN EXISTING LARGE APP TO JETPACK COMPOSE - AHMED TIKIWA

Slide 13

Slide 13 text

ADOPTING COMPOSE: CHOOSING THE APPROACH

Slide 14

Slide 14 text

The bottom-up approach starts migrating the smaller UI elements on the screen, like a Button or a TextView, followed by its ViewGroup elements until everything is converted to composable functions. 
 The top-down approach starts migrating the fragments or view containers, like a FrameLayout, ConstraintLayout, or RecyclerView, followed by the smaller UI elements on the screen. ADOPTING COMPOSE: CHOOSING THE APPROACH FROM XML TO COMPOSE, MY JOURNEY OF TRANSFORMING AN EXISTING LARGE APP TO JETPACK COMPOSE - AHMED TIKIWA

Slide 15

Slide 15 text

WHAT DOES INTEROPERABILITY LOOK LIKE

Slide 16

Slide 16 text

Each fragment has an associated XML layout Gradual migration Use of ComposeView Introduce Compose UI content into XML layout A container WHAT DOES INTEROPERABILITY LOOK LIKE FROM XML TO COMPOSE, MY JOURNEY OF TRANSFORMING AN EXISTING LARGE APP TO JETPACK COMPOSE - AHMED TIKIWA

Slide 17

Slide 17 text

FROM XML TO COMPOSE, MY JOURNEY OF TRANSFORMING AN EXISTING LARGE APP TO JETPACK COMPOSE - AHMED TIKIWA WHAT DOES INTEROPERABILITY LOOK LIKE Removed TextViews RecyclerViews NestedScrollView ConstraintLayout LinearProgressIndicator Added ComposeView /res/layout/fragment_search.xml

Slide 18

Slide 18 text

FROM XML TO COMPOSE, MY JOURNEY OF TRANSFORMING AN EXISTING LARGE APP TO JETPACK COMPOSE - AHMED TIKIWA WHAT DOES INTEROPERABILITY LOOK LIKE @OptIn(ExperimentalMaterialApi::class, ExperimentalComposeUiApi::class) override fun onCreateView(...): View { ... binding.composeContainer.apply { setViewCompositionStrategy( ViewCompositionStrategy .DisposeOnViewTreeLifecycleDestroyed ) setContent { MdcTheme { SearchScreen(navController = findNavController()) } } } return binding.root } /ui/search/SearchFragment.kt

Slide 19

Slide 19 text

FROM XML TO COMPOSE, MY JOURNEY OF TRANSFORMING AN EXISTING LARGE APP TO JETPACK COMPOSE - AHMED TIKIWA WHAT DOES INTEROPERABILITY LOOK LIKE @OptIn(ExperimentalMaterialApi::class, ExperimentalComposeUiApi::class) override fun onCreateView(...): View { ... binding.composeContainer.apply { setViewCompositionStrategy( ViewCompositionStrategy .DisposeOnViewTreeLifecycleDestroyed ) setContent { MdcTheme { SearchScreen(navController = findNavController()) } } } return binding.root } /ui/search/SearchFragment.kt

Slide 20

Slide 20 text

FROM XML TO COMPOSE, MY JOURNEY OF TRANSFORMING AN EXISTING LARGE APP TO JETPACK COMPOSE - AHMED TIKIWA WHAT DOES INTEROPERABILITY LOOK LIKE @OptIn(ExperimentalMaterialApi::class, ExperimentalComposeUiApi::class) override fun onCreateView(...): View { ... binding.composeContainer.apply { setViewCompositionStrategy( ViewCompositionStrategy .DisposeOnViewTreeLifecycleDestroyed ) setContent { MdcTheme { SearchScreen(navController = findNavController()) } } } return binding.root } /ui/search/SearchFragment.kt

Slide 21

Slide 21 text

BREAKING DOWN THE SCREEN CHANGES SEARCH SCREEN

Slide 22

Slide 22 text

BREAKING DOWN THE CHANGES: SEARCH SCREEN Focus on: Search bar Search results list The search result item Displaying the list Removing RecyclerView adapter, ViewHolder and item layout FROM XML TO COMPOSE, MY JOURNEY OF TRANSFORMING AN EXISTING LARGE APP TO JETPACK COMPOSE - AHMED TIKIWA

Slide 23

Slide 23 text

FROM XML TO COMPOSE, MY JOURNEY OF TRANSFORMING AN EXISTING LARGE APP TO JETPACK COMPOSE - AHMED TIKIWA BREAKING DOWN THE CHANGES: SEARCH SCREEN @ExperimentalMaterialApi @ExperimentalComposeUiApi @Composable fun SearchScreen( viewModel: SearchViewModel = hiltViewModel(), navController: NavController ) { ... } /ui/search/SearchScreen.kt

Slide 24

Slide 24 text

Built around composable functions De f ine app’s UI Provide data to be displayed No more focus on UI construction process WHAT IS A COMPOSABLE?

Slide 25

Slide 25 text

FROM XML TO COMPOSE, MY JOURNEY OF TRANSFORMING AN EXISTING LARGE APP TO JETPACK COMPOSE - AHMED TIKIWA BREAKING DOWN THE CHANGES: SEARCH SCREEN @ExperimentalMaterialApi @ExperimentalComposeUiApi @Composable fun SearchScreen( viewModel: SearchViewModel = hiltViewModel(), navController: NavController ) { val searchResultsList = viewModel.searchResponse.observeAsState() val isLoading = viewModel.isLoading.observeAsState() Surface(modifier = Modifier.fillMaxSize()) { Column( modifier = Modifier.padding(8.dp) ) { ... } } } /ui/search/SearchScreen.kt

Slide 26

Slide 26 text

FROM XML TO COMPOSE, MY JOURNEY OF TRANSFORMING AN EXISTING LARGE APP TO JETPACK COMPOSE - AHMED TIKIWA BREAKING DOWN THE CHANGES: SEARCH SCREEN @ExperimentalMaterialApi @ExperimentalComposeUiApi @Composable fun SearchScreen( viewModel: SearchViewModel = hiltViewModel(), navController: NavController ) { val searchResultsList = viewModel.searchResponse.observeAsState() val isLoading = viewModel.isLoading.observeAsState() Surface(modifier = Modifier.fillMaxSize()) { Column( modifier = Modifier.padding(8.dp) ) { ... } } } /ui/search/SearchScreen.kt

Slide 27

Slide 27 text

FROM XML TO COMPOSE, MY JOURNEY OF TRANSFORMING AN EXISTING LARGE APP TO JETPACK COMPOSE - AHMED TIKIWA BREAKING DOWN THE CHANGES: SEARCH SCREEN @ExperimentalMaterialApi @ExperimentalComposeUiApi @Composable fun SearchScreen( viewModel: SearchViewModel = hiltViewModel(), navController: NavController ) { val searchResultsList = viewModel.searchResponse.observeAsState() val isLoading = viewModel.isLoading.observeAsState() Surface(modifier = Modifier.fillMaxSize()) { Column( modifier = Modifier.padding(8.dp) ) { ... } } } /ui/search/SearchScreen.kt

Slide 28

Slide 28 text

FROM XML TO COMPOSE, MY JOURNEY OF TRANSFORMING AN EXISTING LARGE APP TO JETPACK COMPOSE - AHMED TIKIWA BREAKING DOWN THE CHANGES: SEARCH SCREEN @ExperimentalMaterialApi @ExperimentalComposeUiApi @Composable fun SearchScreen( viewModel: SearchViewModel = hiltViewModel(), navController: NavController ) { val searchResultsList = viewModel.searchResponse.observeAsState() val isLoading = viewModel.isLoading.observeAsState() Surface(modifier = Modifier.fillMaxSize()) { Column( modifier = Modifier.padding(8.dp) ) { ... } } } /ui/search/SearchScreen.kt

Slide 29

Slide 29 text

FROM XML TO COMPOSE, MY JOURNEY OF TRANSFORMING AN EXISTING LARGE APP TO JETPACK COMPOSE - AHMED TIKIWA BREAKING DOWN THE CHANGES: SEARCH SCREEN @ExperimentalMaterialApi @ExperimentalComposeUiApi @Composable fun SearchScreen( viewModel: SearchViewModel = hiltViewModel(), navController: NavController ) { val searchResultsList = viewModel.searchResponse.observeAsState() val isLoading = viewModel.isLoading.observeAsState() Surface(modifier = Modifier.fillMaxSize()) { Column( modifier = Modifier.padding(8.dp) ) { ... } } } /ui/search/SearchScreen.kt

Slide 30

Slide 30 text

FROM XML TO COMPOSE, MY JOURNEY OF TRANSFORMING AN EXISTING LARGE APP TO JETPACK COMPOSE - AHMED TIKIWA BREAKING DOWN THE CHANGES: SEARCH SCREEN @ExperimentalMaterialApi @ExperimentalComposeUiApi @Composable fun SearchScreen( viewModel: SearchViewModel = hiltViewModel(), navController: NavController ) { val searchResultsList = viewModel.searchResponse.observeAsState() val isLoading = viewModel.isLoading.observeAsState() Surface(modifier = Modifier.fillMaxSize()) { Column( modifier = Modifier.padding(8.dp) ) { ... } } } /ui/search/SearchScreen.kt

Slide 31

Slide 31 text

FROM XML TO COMPOSE, MY JOURNEY OF TRANSFORMING AN EXISTING LARGE APP TO JETPACK COMPOSE - AHMED TIKIWA BREAKING DOWN THE CHANGES: SEARCH SCREEN @ExperimentalMaterialApi @ExperimentalComposeUiApi @Composable fun SearchScreen(...) { ... Surface(modifier = Modifier.fillMaxSize()) { Column( modifier = Modifier.padding(8.dp) ) { Box(modifier = Modifier.fillMaxSize()) { ... } } } } /ui/search/SearchScreen.kt

Slide 32

Slide 32 text

FROM XML TO COMPOSE, MY JOURNEY OF TRANSFORMING AN EXISTING LARGE APP TO JETPACK COMPOSE - AHMED TIKIWA BREAKING DOWN THE CHANGES: SEARCH SCREEN @ExperimentalMaterialApi @ExperimentalComposeUiApi @Composable fun SearchScreen(...) { ... Box(modifier = Modifier.fillMaxSize()) { SearchArea() if (isLoading.value == true) { LinearProgressIndicator( modifier = Modifier .padding(8.dp) .fillMaxWidth() ) } } } } /ui/search/SearchScreen.kt

Slide 33

Slide 33 text

FROM XML TO COMPOSE, MY JOURNEY OF TRANSFORMING AN EXISTING LARGE APP TO JETPACK COMPOSE - AHMED TIKIWA BREAKING DOWN THE CHANGES: SEARCH SCREEN @ExperimentalMaterialApi @ExperimentalComposeUiApi @Composable fun SearchScreen(...) { ... Box(modifier = Modifier.fillMaxSize()) { SearchArea() if (isLoading.value == true) { LinearProgressIndicator( modifier = Modifier .padding(8.dp) .fillMaxWidth() ) } } } } /ui/search/SearchScreen.kt

Slide 34

Slide 34 text

FROM XML TO COMPOSE, MY JOURNEY OF TRANSFORMING AN EXISTING LARGE APP TO JETPACK COMPOSE - AHMED TIKIWA BREAKING DOWN THE CHANGES: SEARCH SCREEN @OptIn(ExperimentalComposeUiApi::class, ExperimentalMaterialApi::class) @Composable fun SearchArea( searchResultsList: List?, onTextSubmit: (query: String) -> Unit, onResultClick: (item: ShowSearch) -> Unit ) { Column(modifier = Modifier.padding(top = 8.dp)) { SearchForm { onTextSubmit(it) } searchResultsList?.let { results -> SearchResultsList(list = results) { onResultClick(it) } } } } /ui/search/SearchScreen.kt

Slide 35

Slide 35 text

FROM XML TO COMPOSE, MY JOURNEY OF TRANSFORMING AN EXISTING LARGE APP TO JETPACK COMPOSE - AHMED TIKIWA BREAKING DOWN THE CHANGES: SEARCH SCREEN @OptIn(ExperimentalComposeUiApi::class, ExperimentalMaterialApi::class) @Composable fun SearchArea( searchResultsList: List?, onTextSubmit: (query: String) -> Unit, onResultClick: (item: ShowSearch) -> Unit ) { Column(modifier = Modifier.padding(top = 8.dp)) { SearchForm { onTextSubmit(it) } searchResultsList?.let { results -> SearchResultsList(list = results) { onResultClick(it) } } } } /ui/search/SearchScreen.kt

Slide 36

Slide 36 text

FROM XML TO COMPOSE, MY JOURNEY OF TRANSFORMING AN EXISTING LARGE APP TO JETPACK COMPOSE - AHMED TIKIWA BREAKING DOWN THE CHANGES: SEARCH SCREEN @OptIn(ExperimentalComposeUiApi::class, ExperimentalMaterialApi::class) @Composable fun SearchArea( searchResultsList: List?, onTextSubmit: (query: String) -> Unit, onResultClick: (item: ShowSearch) -> Unit ) { Column(modifier = Modifier.padding(top = 8.dp)) { SearchForm { onTextSubmit(it) } searchResultsList?.let { results -> SearchResultsList(list = results) { onResultClick(it) } } } } /ui/search/SearchScreen.kt

Slide 37

Slide 37 text

FROM XML TO COMPOSE, MY JOURNEY OF TRANSFORMING AN EXISTING LARGE APP TO JETPACK COMPOSE - AHMED TIKIWA BREAKING DOWN THE CHANGES: SEARCH SCREEN @OptIn(ExperimentalComposeUiApi::class, ExperimentalMaterialApi::class) @Composable fun SearchArea( searchResultsList: List?, onTextSubmit: (query: String) -> Unit, onResultClick: (item: ShowSearch) -> Unit ) { Column(modifier = Modifier.padding(top = 8.dp)) { SearchForm { onTextSubmit(it) } searchResultsList?.let { results -> SearchResultsList(list = results) { onResultClick(it) } } } } /ui/search/SearchScreen.kt

Slide 38

Slide 38 text

FROM XML TO COMPOSE, MY JOURNEY OF TRANSFORMING AN EXISTING LARGE APP TO JETPACK COMPOSE - AHMED TIKIWA BREAKING DOWN THE CHANGES: SEARCH SCREEN @OptIn(ExperimentalComposeUiApi::class, ExperimentalMaterialApi::class) @Composable fun SearchArea( searchResultsList: List?, onTextSubmit: (query: String) -> Unit, onResultClick: (item: ShowSearch) -> Unit ) { Column(modifier = Modifier.padding(top = 8.dp)) { SearchForm { onTextSubmit(it) } searchResultsList?.let { results -> SearchResultsList(list = results) { onResultClick(it) } } } } /ui/search/SearchScreen.kt @ExperimentalComposeUiApi @Composable fun SearchForm( onSearch: (String) -> Unit ) { val searchQueryState = rememberSaveable { mutableStateOf("") } SearchInputField( inputLabel = stringResource(id = R.string.search_input_hint), valueState = searchQueryState, onValueChange = { onSearch(searchQueryState.value.trim()) } ) }

Slide 39

Slide 39 text

FROM XML TO COMPOSE, MY JOURNEY OF TRANSFORMING AN EXISTING LARGE APP TO JETPACK COMPOSE - AHMED TIKIWA BREAKING DOWN THE CHANGES: SEARCH SCREEN @OptIn(ExperimentalComposeUiApi::class, ExperimentalMaterialApi::class) @Composable fun SearchArea( searchResultsList: List?, onTextSubmit: (query: String) -> Unit, onResultClick: (item: ShowSearch) -> Unit ) { Column(modifier = Modifier.padding(top = 8.dp)) { SearchForm { onTextSubmit(it) } searchResultsList?.let { results -> SearchResultsList(list = results) { onResultClick(it) } } } } /ui/search/SearchScreen.kt @ExperimentalComposeUiApi @Composable fun SearchForm( onSearch: (String) -> Unit ) { val searchQueryState = rememberSaveable { mutableStateOf("") } SearchInputField( inputLabel = stringResource(id = R.string.search_input_hint), valueState = searchQueryState, onValueChange = { onSearch(searchQueryState.value.trim()) } ) }

Slide 40

Slide 40 text

FROM XML TO COMPOSE, MY JOURNEY OF TRANSFORMING AN EXISTING LARGE APP TO JETPACK COMPOSE - AHMED TIKIWA BREAKING DOWN THE CHANGES: SEARCH SCREEN @OptIn(ExperimentalComposeUiApi::class, ExperimentalMaterialApi::class) @Composable fun SearchArea( searchResultsList: List?, onTextSubmit: (query: String) -> Unit, onResultClick: (item: ShowSearch) -> Unit ) { Column(modifier = Modifier.padding(top = 8.dp)) { SearchForm { onTextSubmit(it) } searchResultsList?.let { results -> SearchResultsList(list = results) { onResultClick(it) } } } } /ui/search/SearchScreen.kt @ExperimentalComposeUiApi @Composable fun SearchForm( onSearch: (String) -> Unit ) { val searchQueryState = rememberSaveable { mutableStateOf("") } SearchInputField( inputLabel = stringResource(id = R.string.search_input_hint), valueState = searchQueryState, onValueChange = { onSearch(searchQueryState.value.trim()) } ) }

Slide 41

Slide 41 text

FROM XML TO COMPOSE, MY JOURNEY OF TRANSFORMING AN EXISTING LARGE APP TO JETPACK COMPOSE - AHMED TIKIWA BREAKING DOWN THE CHANGES: SEARCH SCREEN @OptIn(ExperimentalComposeUiApi::class, ExperimentalMaterialApi::class) @Composable fun SearchArea( searchResultsList: List?, onTextSubmit: (query: String) -> Unit, onResultClick: (item: ShowSearch) -> Unit ) { Column(modifier = Modifier.padding(top = 8.dp)) { SearchForm { onTextSubmit(it) } searchResultsList?.let { results -> SearchResultsList(list = results) { onResultClick(it) } } } } /ui/search/SearchScreen.kt @ExperimentalComposeUiApi @Composable fun SearchForm( onSearch: (String) -> Unit ) { val searchQueryState = rememberSaveable { mutableStateOf("") } SearchInputField( inputLabel = stringResource(id = R.string.search_input_hint), valueState = searchQueryState, onValueChange = { onSearch(searchQueryState.value.trim()) } ) }

Slide 42

Slide 42 text

FROM XML TO COMPOSE, MY JOURNEY OF TRANSFORMING AN EXISTING LARGE APP TO JETPACK COMPOSE - AHMED TIKIWA BREAKING DOWN THE CHANGES: SEARCH SCREEN @OptIn(ExperimentalComposeUiApi::class, ExperimentalMaterialApi::class) @Composable fun SearchArea( searchResultsList: List?, onTextSubmit: (query: String) -> Unit, onResultClick: (item: ShowSearch) -> Unit ) { Column(modifier = Modifier.padding(top = 8.dp)) { SearchForm { onTextSubmit(it) } searchResultsList?.let { results -> SearchResultsList(list = results) { onResultClick(it) } } } } /ui/search/SearchScreen.kt @ExperimentalComposeUiApi @Composable fun SearchForm( onSearch: (String) -> Unit ) { val searchQueryState = rememberSaveable { mutableStateOf("") } SearchInputField( inputLabel = stringResource(id = R.string.search_input_hint), valueState = searchQueryState, onValueChange = { onSearch(searchQueryState.value.trim()) } ) }

Slide 43

Slide 43 text

FROM XML TO COMPOSE, MY JOURNEY OF TRANSFORMING AN EXISTING LARGE APP TO JETPACK COMPOSE - AHMED TIKIWA BREAKING DOWN THE CHANGES: SEARCH SCREEN @ExperimentalComposeUiApi @Composable fun SearchForm( onSearch: (String) -> Unit ) { val searchQueryState = rememberSaveable { mutableStateOf("") } SearchInputField( inputLabel = stringResource(id = R.string.search_input_hint), valueState = searchQueryState, onValueChange = { onSearch(searchQueryState.value.trim()) } ) } /ui/search/SearchScreen.kt @Composable fun SearchInputField( modifier: Modifier = Modifier, inputLabel: String, valueState: MutableState, onValueChange: (value: String) -> Unit ) { OutlinedTextField( value = valueState.value, onValueChange = { valueState.value = it onValueChange(valueState.value) }, label = { Text(inputLabel) }, singleLine = true, modifier = modifier .padding(8.dp) .fillMaxWidth() ) }

Slide 44

Slide 44 text

FROM XML TO COMPOSE, MY JOURNEY OF TRANSFORMING AN EXISTING LARGE APP TO JETPACK COMPOSE - AHMED TIKIWA BREAKING DOWN THE CHANGES: SEARCH SCREEN /ui/search/SearchScreen.kt @Composable fun SearchInputField( modifier: Modifier = Modifier, inputLabel: String, valueState: MutableState, onValueChange: (value: String) -> Unit ) { OutlinedTextField( value = valueState.value, onValueChange = { valueState.value = it onValueChange(valueState.value) }, label = { Text(inputLabel) }, singleLine = true, modifier = modifier .padding(8.dp) .fillMaxWidth() ) } @ExperimentalComposeUiApi @Composable fun SearchForm( onSearch: (String) -> Unit ) { val searchQueryState = rememberSaveable { mutableStateOf("") } SearchInputField( inputLabel = stringResource(id = R.string.search_input_hint), valueState = searchQueryState, onValueChange = { onSearch(searchQueryState.value.trim()) } ) }

Slide 45

Slide 45 text

FROM XML TO COMPOSE, MY JOURNEY OF TRANSFORMING AN EXISTING LARGE APP TO JETPACK COMPOSE - AHMED TIKIWA BREAKING DOWN THE CHANGES: SEARCH SCREEN /ui/search/SearchScreen.kt @Composable fun SearchInputField( modifier: Modifier = Modifier, inputLabel: String, valueState: MutableState, onValueChange: (value: String) -> Unit ) { OutlinedTextField( value = valueState.value, onValueChange = { valueState.value = it onValueChange(valueState.value) }, label = { Text(inputLabel) }, singleLine = true, modifier = modifier .padding(8.dp) .fillMaxWidth() ) } @ExperimentalComposeUiApi @Composable fun SearchForm( onSearch: (String) -> Unit ) { val searchQueryState = rememberSaveable { mutableStateOf("") } SearchInputField( inputLabel = stringResource(id = R.string.search_input_hint), valueState = searchQueryState, onValueChange = { onSearch(searchQueryState.value.trim()) } ) }

Slide 46

Slide 46 text

FROM XML TO COMPOSE, MY JOURNEY OF TRANSFORMING AN EXISTING LARGE APP TO JETPACK COMPOSE - AHMED TIKIWA BREAKING DOWN THE CHANGES: SEARCH SCREEN @OptIn(ExperimentalComposeUiApi::class, ExperimentalMaterialApi::class) @Composable fun SearchArea( searchResultsList: List?, onTextSubmit: (query: String) -> Unit, onResultClick: (item: ShowSearch) -> Unit ) { Column(modifier = Modifier.padding(top = 8.dp)) { SearchForm { onTextSubmit(it) } searchResultsList?.let { results -> SearchResultsList(list = results) { onResultClick(it) } } } } /ui/search/SearchScreen.kt @ExperimentalMaterialApi @Composable fun SearchResultsList( list: List, onClick: (item: ShowSearch) -> Unit ) { LazyColumn { items(list) { SearchListCard(item = it) { onClick(it) } } } }

Slide 47

Slide 47 text

FROM XML TO COMPOSE, MY JOURNEY OF TRANSFORMING AN EXISTING LARGE APP TO JETPACK COMPOSE - AHMED TIKIWA BREAKING DOWN THE CHANGES: SEARCH SCREEN @OptIn(ExperimentalComposeUiApi::class, ExperimentalMaterialApi::class) @Composable fun SearchArea( searchResultsList: List?, onTextSubmit: (query: String) -> Unit, onResultClick: (item: ShowSearch) -> Unit ) { Column(modifier = Modifier.padding(top = 8.dp)) { SearchForm { onTextSubmit(it) } searchResultsList?.let { results -> SearchResultsList(list = results) { onResultClick(it) } } } } /ui/search/SearchScreen.kt @ExperimentalMaterialApi @Composable fun SearchResultsList( list: List, onClick: (item: ShowSearch) -> Unit ) { LazyColumn { items(list) { SearchListCard(item = it) { onClick(it) } } } }

Slide 48

Slide 48 text

FROM XML TO COMPOSE, MY JOURNEY OF TRANSFORMING AN EXISTING LARGE APP TO JETPACK COMPOSE - AHMED TIKIWA BREAKING DOWN THE CHANGES: SEARCH SCREEN @ExperimentalMaterialApi @ExperimentalComposeUiApi @Composable fun SearchScreen(...) { ... Box(modifier = Modifier.fillMaxSize()) { SearchArea() if (isLoading.value == true) { LinearProgressIndicator( modifier = Modifier .padding(8.dp) .fillMaxWidth() ) } } } } /ui/search/SearchScreen.kt SearchArea( searchResultsList = searchResultsList.value, onResultClick = { val directions = SearchFragmentDirections.actionSearchFragmentToSh owDetailFragment( ShowDetailArg( source = "search", showId = it.id, showTitle = it.name, showImageUrl = it.originalImageUrl, showBackgroundUrl = it.mediumImageUrl ) ) navController.navigate(directions) }, onTextSubmit = { viewModel.onQueryTextSubmit(it) } )

Slide 49

Slide 49 text

FROM XML TO COMPOSE, MY JOURNEY OF TRANSFORMING AN EXISTING LARGE APP TO JETPACK COMPOSE - AHMED TIKIWA BREAKING DOWN THE CHANGES: SEARCH SCREEN @ExperimentalMaterialApi @ExperimentalComposeUiApi @Composable fun SearchScreen(...) { ... Box(modifier = Modifier.fillMaxSize()) { SearchArea() if (isLoading.value == true) { LinearProgressIndicator( modifier = Modifier .padding(8.dp) .fillMaxWidth() ) } } } } /ui/search/SearchScreen.kt SearchArea( searchResultsList = searchResultsList.value, onResultClick = { val directions = SearchFragmentDirections.actionSearchFragmentToSh owDetailFragment( ShowDetailArg( source = "search", showId = it.id, showTitle = it.name, showImageUrl = it.originalImageUrl, showBackgroundUrl = it.mediumImageUrl ) ) navController.navigate(directions) }, onTextSubmit = { viewModel.onQueryTextSubmit(it) } )

Slide 50

Slide 50 text

FROM XML TO COMPOSE, MY JOURNEY OF TRANSFORMING AN EXISTING LARGE APP TO JETPACK COMPOSE - AHMED TIKIWA BREAKING DOWN THE CHANGES: SEARCH SCREEN @ExperimentalMaterialApi @ExperimentalComposeUiApi @Composable fun SearchScreen(...) { ... Box(modifier = Modifier.fillMaxSize()) { SearchArea() if (isLoading.value == true) { LinearProgressIndicator( modifier = Modifier .padding(8.dp) .fillMaxWidth() ) } } } } /ui/search/SearchScreen.kt SearchArea( searchResultsList = searchResultsList.value, onResultClick = { val directions = SearchFragmentDirections.actionSearchFragmentToSh owDetailFragment( ShowDetailArg( source = "search", showId = it.id, showTitle = it.name, showImageUrl = it.originalImageUrl, showBackgroundUrl = it.mediumImageUrl ) ) navController.navigate(directions) }, onTextSubmit = { viewModel.onQueryTextSubmit(it) } )

Slide 51

Slide 51 text

BEFORE AND AFTER’S

Slide 52

Slide 52 text

BEFORE AND AFTER: SEARCH SCREEN FROM XML TO COMPOSE, MY JOURNEY OF TRANSFORMING AN EXISTING LARGE APP TO JETPACK COMPOSE - AHMED TIKIWA SearchFragment.kt @Composable SearchScreen() @Composable 
 SearchForm() @Composable 
 SearchResultsList()

Slide 53

Slide 53 text

BEFORE AND AFTER: DASHBOARD SCREEN FROM XML TO COMPOSE, MY JOURNEY OF TRANSFORMING AN EXISTING LARGE APP TO JETPACK COMPOSE - AHMED TIKIWA DashboardFragment.kt @Composable DashboardScreen() @Composable 
 ShowsRow() 
 & uses LazyRow() @Composable 
 ShowsRow() 
 & uses LazyRow()

Slide 54

Slide 54 text

BEFORE AND AFTER: EXPLORE SCREEN FROM XML TO COMPOSE, MY JOURNEY OF TRANSFORMING AN EXISTING LARGE APP TO JETPACK COMPOSE - AHMED TIKIWA ExploreFragment.kt @Composable ExploreScreen @Composable 
 TrendingShowsRow() 
 & uses LazyRow() @Composable 
 PopularShowsRow() 
 & uses LazyRow

Slide 55

Slide 55 text

BEFORE AND AFTER: SHOW DETAIL SCREEN FROM XML TO COMPOSE, MY JOURNEY OF TRANSFORMING AN EXISTING LARGE APP TO JETPACK COMPOSE - AHMED TIKIWA ShowDetailFragment.kt @Composable ShowDetailScreen @Composable 
 BackdropAndTitle() @Composable 
 PosterAndMetadata() @Composable 
 Text()

Slide 56

Slide 56 text

FROM XML TO COMPOSE, MY JOURNEY OF TRANSFORMING AN EXISTING LARGE APP TO JETPACK COMPOSE - AHMED TIKIWA ShowDetailFragment.kt @Composable ShowDetailScreen @Composable 
 PreviousEpisode() @Composable 
 ShowDetailButtons @Composable 
 ShowCastList() 
 & uses LazyRow() BEFORE AND AFTER: SHOW DETAIL SCREEN

Slide 57

Slide 57 text

FROM XML TO COMPOSE, MY JOURNEY OF TRANSFORMING AN EXISTING LARGE APP TO JETPACK COMPOSE - AHMED TIKIWA ShowDetailFragment.kt @Composable ShowDetailScreen @Composable 
 PreviousEpisode() @Composable 
 TraktRatingSummary() BEFORE AND AFTER: SHOW DETAIL SCREEN

Slide 58

Slide 58 text

BREAKING DOWN THE CHANGES: SEARCH SCREEN FROM XML TO COMPOSE, MY JOURNEY OF TRANSFORMING AN EXISTING LARGE APP TO JETPACK COMPOSE - AHMED TIKIWA @Composable ShowSeasonEpisodesScreen ShowSeasonsEpisodesFragment.kt @Composable 
 SectionHeadingText() @Composable 
 ShowSeasonEpisodes() 
 & uses LazyColumn() @Composable 
 ShowSeasonEpisodeCard()

Slide 59

Slide 59 text

BREAKING DOWN THE CHANGES: SEARCH SCREEN FROM XML TO COMPOSE, MY JOURNEY OF TRANSFORMING AN EXISTING LARGE APP TO JETPACK COMPOSE - AHMED TIKIWA TraktAccountFragment.kt @Composable TraktAccountScreen() @Composable 
 FavoritesList() 
 & uses LazyVerticalGrid() @Composable 
 ListPosterCard() @Composable 
 SectionHeadingText()

Slide 60

Slide 60 text

WHAT STILL NEEDS TO BE UPDATED OR CONVERTED FROM XML TO COMPOSE, MY JOURNEY OF TRANSFORMING AN EXISTING LARGE APP TO JETPACK COMPOSE - AHMED TIKIWA Toolbar Bottom navigation bar Migration to Compose Navigation Removal of all fragment f iles Replace Surface with Sca ff old Replacement of observeAsState with MutableState observation Add animations Add tests for Composables

Slide 61

Slide 61 text

O ff icial Compose Documentation 
 https://developer.android.com/jetpack/compose O ff icial Compose course 
 https://developer.android.com/courses/pathways/compose Compose Layout Basics https://developer.android.com/jetpack/compose/layouts/basics State Hoisting https://developer.android.com/jetpack/compose/state#state-hoisting UpNext TV Series Manager code is available as an open-source project and part of the Google Developers Dev Library https://devlibrary.withgoogle.com/products/android/repos/akitikkx-upnext. Click on the “View on Github” to see the code. Contributions welcome from the community! Please read my Readme and contribution Guidelines for more information RESOURCES FROM XML TO COMPOSE, MY JOURNEY OF TRANSFORMING AN EXISTING LARGE APP TO JETPACK COMPOSE - AHMED TIKIWA

Slide 62

Slide 62 text

FROM XML TO COMPOSE, MY JOURNEY OF TRANSFORMING AN EXISTING LARGE APP TO JETPACK COMPOSE AHMED TIKIWA SENIOR SOFTWARE ENGINEER - ANDROID @ LUNO @ahmed_tikiwa