SlideShare a Scribd company logo
© Instil Software 2020
Compose Part 2
(the practice)
Belfast Google Dev Group
September 2022
@kelvinharron
https://instil.co
– Short history tour of Android UI Development.
– Examples of using Jetpack Compose in across multiple
apps.
– Focus on Navigation, Accessibility & Testing.
– Sneak peak at an upcoming Instil app!
AGENDA
1
A history of
Android UI dev
The classic - findViewById XML layout
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
id used by Activity
The classic - findViewById Activity
private lateinit var textView: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
textView = findViewById<TextView?>(R.id.textView).apply {
text = "Hello GDE"
textSize = 42f
setTextColor(Color.CYAN)
}
}
Instil recommendation - Databinding
<layout ...>
<data>
<import type="android.view.View"/>
<variable
name="vm"
type="co.instil.databinding.DemoViewModel"/>
</data>
<TextView
...
android:text="@{vm.text}"/>
</LinearLayout>
</layout>
Databinding XML
layout as root
source of truth
binded field
class DemoActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = DataBindingUtil.setContentView<ActivityDemoBinding>
(this,R.layout.activity_demo)
binding.vm = DemoViewModel()
}
}
Databinding Activity
XML layout file
setting vm on XML
class DemoViewModel {
val text = ObservableField("Data binding works!")
}
ViewModel
The shiny new thing, Jetpack Compose!
UI representing State
… but starting from scratch?
2
Working with
Compose
WIP
Sharing Components
Previews that work!
@Composable
@Preview(uiMode = UI_MODE_NIGHT_YES)
fun CarouselScreenDarkPreview() {
EstiMateTheme {
CarouselScreen(
{},
{ CardSelection.Nuclear },
CardSelection.Nuclear
)
}
}
Card(
backgroundColor = instilGreen,
shape = RoundedCornerShape(size = 16.dp),
elevation = 8.dp,
modifier = modifier
.padding(8.dp)
.width(120.dp)
.height(150.dp)
.clickable { onSelected(selection) },
) {
CardContent(
selection = selection,
fontSize = fontSize
)
}
EstiMateCard
default modifier
material component
Box(contentAlignment = Alignment.Center) {
when (selection) {
is CoffeeBreak -> CoffeeImage()
is Nuclear -> NuclearImage()
else -> SelectionText(selection, fontSize)
}
}
CardContent
`Flow` of data
StateFlow<List<Player>>
ViewModel
Composable
Screen
Jetpack
DataStore
Firebase
Flow<List<Player>>
List<Player>
open fun getPlayers(teamName: String): Flow<List<Player>> = callbackFlow {
firebaseService.observePlayersIn(teamName) { players ->
trySend(players)
}
awaitClose { channel.close() }
}
PlayerService calling to Firebase
posting a fresh list of players
val players: StateFlow<List<Player>> = playerService.getPlayers()
.stateIn(
scope = viewModelScope,
started = SharingStarted.Lazily,
initialValue = emptyList()
)
ViewModel called to PlayerService
cancels the work once vm cleared
val votedPlayers: List<Player> by viewModel.players.collectAsState()
...
Composable calling to ViewModel
LazyColumn(modifier = Modifier.fillMaxWidth()) {
items(votedPlayers.size) { index ->
UserVoteItem(votedPlayers[index])
}
}
composable per list item
listOf(
Player(CardSelection.Five, "Kelvin"),
Player(CardSelection.Three, "Garth")
)
– Started in 2016.
– MVP pattern through use of interfaces.
– Jetpack Compose introduced as we took ownership of
development.
– Shipped 2 new UI driven features with Jetpack Compose.
Vypr
Android App
Bumping old dependencies
Using Jetpack Compose in XML Views
<androidx.compose.ui.platform.ComposeView
android:id="@+id/compose_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
reference id
Using Jetpack Compose in XML Views
private var _binding: FragmentSteersBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentSteersBinding.inflate(inflater, container, false)
val view = binding.root
binding.composeView.apply {
setViewCompositionStrategy(DisposeOnViewTreeLifecycleDestroyed)
setContent {
VyprTheme {
SteersListView()
}
}
}
return view
}
compose_view xml element
our new composable
Using XML views in Jetpack Compose
AndroidView(
modifier = Modifier.fillMaxSize(),
factory = { context ->
AspectRatioImageView(context).apply {
load(steer.previewImageUrl)
setOnClickListener { onSteerClicked(steer) }
}
}
)
legacy view with lots of scary
– RecyclerView & adapter complexity removed.
– Jetpack Compose views driven by lifecycle aware ViewModel.
– More testable implementation.
– We fixed the bug!
Steers List Interop example
3
Navigation
Activity to Activity
Activities with Fragments
Single Activity Architecture
Jetpack Compose Navigation!
– NavController - central API for stateful navigation.
– NavHost - links NavController with a navigation graph.
– Each Composable screen is known as a route.
Jetpack Navigation
Now supporting Compose
Compose Navigation!
NavHost(navController = navController, startDestination = "profile") {
composable("profile") { Profile(/*...*/) }
composable("friendslist") { FriendsList(/*...*/) }
/*...*/
}
Navigating to another Route
navController.navigate("friendslist")
Compose Navigation
With Arguments
NavHost(startDestination = "profile/{userId}") {
...
composable(
"profile/{userId}",
arguments = listOf(navArgument("userId") { type = NavType.StringType
})
) {...}
}
...
navController.navigate("profile/user1234")
compose-destinations
A KSP library that processes annotations and
generates code that uses Official Jetpack
Compose Navigation under the hood.
It hides the complex, non-type-safe and boilerplate
code you would have to write otherwise.
Rafael
Costa
github.com/raamcosta/compose-destinations
Adding a Destination
@Destination(start = true)
@Composable
fun LoginRoute(
destinationsNavigator: DestinationsNavigator
) {
LoginScreen(
...
)
}
tag composable for generation
provided for nav
Generated NavGraph
object NavGraphs {
val root = NavGraph(
route = "root",
startRoute = LoginRouteDestination,
destinations = listOf(
CarouselRouteDestination,
LoginRouteDestination,
ResultsRouteDestination,
SelectionRouteDestination
)
)
}
each annotated composable
labelled start
Type safe navigation
onLoginClicked = {
destinationsNavigator.navigate(SelectionRouteDestination)
},
Type safe navigation with args
onCardSelected = { cardSelection ->
destinationsNavigator.navigate(
CarouselRouteDestination(selection = cardSelection)
)
})
Custom Serializer
@NavTypeSerializer
class CardSelectionSerializer : DestinationsNavTypeSerializer<CardSelection> {
override fun fromRouteString(routeStr: String): CardSelection {
return CardSelection.fromString(routeStr)
}
override fun toRouteString(value: CardSelection): String {
return value.name
}
}
Under the hood
object LoginRouteDestination : DirectionDestination {
operator fun invoke() = this
@get:RestrictTo(RestrictTo.Scope.SUBCLASSES)
override val baseRoute = "login_route"
override val route = baseRoute
@Composable
override fun DestinationScope<Unit>.Content(
dependenciesContainerBuilder: @Composable DependenciesContainerBuilder<Unit>.() -> Unit
) {
LoginRoute(
destinationsNavigator = destinationsNavigator
)
}
}
our composable
4
Accessibility
What is Accessibility?
“Mobile accessibility” refers to making websites
and applications more accessible to people with
disabilities when they are using mobile phones
and other devices.
Shawn Lawton
Henry
w3.org/WAI/standards-guidelines/mobile/
Android Accessibility
– Switch Access: interact with switches instead of the
touchscreen.
– BrailleBack: refreshable Braille display to an Android device
over Bluetooth.
– Voice Access: control an Android device with spoken
commands.
– TalkBack: spoken feedback for UI interactions.
What options are baked into the OS?
TalkBack
An essential tool for every Android team
Play Store Testing
Play Store Testing - Example Issue
Screen Reader issue confirmed
no context of dialog
Fixing the Dialog
val accountDeletionDialogAccessibilityLabel = stringResource(
id = R.string.accessibility_account_deletion_delete_dialog
)
AlertDialog(
...
modifier = Modifier.semantics(mergeDescendants = true) {
contentDescription = accountDeletionDialogAccessibilityLabel
}
)
Fixed!
Accessibility Starts with Design
Screen Sizes & Resolution
UI Mockups
– Discuss what the UI toolkit can do when size is
constrained. Compose is good at scaling text!
– Agree how to handle view scaling.
– Agree copy for accessibility labelling.
– Collaborate with designers & product owners.
5
Testing
Compose Semantics
Semantics, as the name implies, give meaning to a
piece of UI. In this context, a "piece of UI" (or element)
can mean anything from a single composable to a full
screen.
The semantics tree is generated alongside the UI
hierarchy, and describes it.
Example Button
Button(
modifier = Modifier.semantics {
contentDescription = "Add to favorites"
}
)
individual ui elements make up a button
easier to find
Test Setup
@get:Rule
val composeTestRule = createAndroidComposeRule<VyPopsActivity>()
@Before
fun beforeEachTest() {
composeTestRule.setContent {
VyprTheme {
VyPopsLandingScreen(EmptyDestinationsNavigator)
}
}
}
Finders
Select one or more elements (nodes) to assert or act on
composeTestRule
.onNodeWithContentDescription("Close Button")
composeTestRule
.onNodeWithText("What happens next")
Finders - Debug Logging
Node #1 at (l=0.0, t=54.0, r=720.0, b=1436.0)px
|-Node #2 at (l=70.0, t=54.0, r=650.0, b=1436.0)px
ContentDescription = '[VyPops Permissions Page]'
|-Node #3 at (l=70.0, t=75.0, r=112.0, b=117.0)px
| Role = 'Button'
| Focused = 'false'
| ContentDescription = '[Close Button]'
| Actions = [OnClick]
| MergeDescendants = 'true'
|-Node #6 at (l=229.0, t=194.0, r=492.0, b=303.0)px
| ContentDescription = '[Vypr Logo]'
| Role = 'Image'
|-Node #7 at (l=91.0, t=687.0, r=133.0, b=729.0)px
| ContentDescription = '[Record Audio Tick]'
| Role = 'Image'
|-Node #8 at (l=147.0, t=684.0, r=615.0, b=731.0)px
| Text = '[Microphone access granted]'
| Actions = [GetTextLayoutResult]
|-Node #9 at (l=125.0, t=762.0, r=167.0, b=804.0)px
| ContentDescription = '[Camera Tick]'
| Role = 'Image'
|-Node #10 at (l=181.0, t=759.0, r=582.0, b=806.0)px
| Text = '[Camera access granted]'
| Actions = [GetTextLayoutResult]
|-Node #11 at (l=84.0, t=1275.0, r=636.0, b=1366.0)px
Text = '[VyPops needs access to both your camera and microphone.]'
Actions = [GetTextLayoutResult]
Node #1 at (l=0.0, t=54.0, r=720.0, b=1436.0)px
|-Node #2 at (l=70.0, t=54.0, r=650.0, b=1436.0)px
ContentDescription = '[VyPops Permissions Page]'
|-Node #3 at (l=70.0, t=75.0, r=112.0, b=117.0)px
| Role = 'Button'
| Focused = 'false'
| Actions = [OnClick]
| MergeDescendants = 'true'
| |-Node #5 at (l=70.0, t=75.0, r=112.0, b=117.0)px
| ContentDescription = '[Close Button]'
| Role = 'Image'
|-Node #6 at (l=229.0, t=194.0, r=492.0, b=303.0)px
| ContentDescription = '[Vypr Logo]'
| Role = 'Image'
|-Node #7 at (l=91.0, t=687.0, r=133.0, b=729.0)px
| ContentDescription = '[Record Audio Tick]'
| Role = 'Image'
|-Node #8 at (l=147.0, t=684.0, r=615.0, b=731.0)px
| Text = '[Microphone access granted]'
| Actions = [GetTextLayoutResult]
|-Node #9 at (l=125.0, t=762.0, r=167.0, b=804.0)px
| ContentDescription = '[Camera Tick]'
| Role = 'Image'
|-Node #10 at (l=181.0, t=759.0, r=582.0, b=806.0)px
| Text = '[Camera access granted]'
| Actions = [GetTextLayoutResult]
|-Node #11 at (l=84.0, t=1275.0, r=636.0, b=1366.0)px
Text = '[VyPops needs access to both your camera and microphone.]'
Actions = [GetTextLayoutResult]
Assertions
Verify elements exist or have certain attributes
composeTestRule
.onNodeWithContentDescription("Login Button")
.assertIsEnabled()
composeTestRule
.onNodeWithText("What happens next")
.assertIsDisplayed()
Simulate user input or gestures
Actions
composeTestRule
.onNodeWithContentDescription("Close Button")
.performClick()
...
.performTouchInput {
swipeLeft()
}
developer.android.com/jetpack/compose/testing-cheatsheet
Shot is a Gradle plugin and a core android library
thought to run screenshot tests for Android.
Pedro
Gómez
github.com/pedrovgs/Shot
./gradlew executeScreenshotTests -Precord
Example Test
@get:Rule
val composeRule = createAndroidComposeRule<AccountDeletionActivity>()
@Test
fun accountDeletedScreenBodyScreenshot() {
composeRule.setContent { AccountDeletedScreenBody() }
compareScreenshot(composeRule)
}
./gradlew executeScreenshotTests
Example Output
6
Introducing Compose
to your team
Introducing Compose
Phased approach
– Do you have an existing app with custom UI
components?
– Recreate them in Compose!
– Provide a foundation to educate your team.
– Define standards & best practices.
Introducing Compose
medium.com/tinder/sparking-jetpack-compose-at-tinder-8f15d77eb4da
7
Conclusions
– We’ve adopted Jetpack Compose for all new Android
projects.
– Excellent official documentation & codelabs available.
– Good tooling and a growing list of third-party libraries
available.
– Recommend new starts prioritise Jetpack Compose over
XML.
Conclusions
EstiMate for Android is coming soon™
Questions?
Questions?

More Related Content

Similar to Compose In Practice

Training Session 2 - Day 2
Training Session 2 - Day 2Training Session 2 - Day 2
Training Session 2 - Day 2
Vivek Bhusal
 
Fragments: Why, How, What For?
Fragments: Why, How, What For?Fragments: Why, How, What For?
Fragments: Why, How, What For?
Brenda Cook
 
How to use data binding in android
How to use data binding in androidHow to use data binding in android
How to use data binding in android
InnovationM
 
Infinum Android Talks #14 - Data binding to the rescue... or not (?) by Krist...
Infinum Android Talks #14 - Data binding to the rescue... or not (?) by Krist...Infinum Android Talks #14 - Data binding to the rescue... or not (?) by Krist...
Infinum Android Talks #14 - Data binding to the rescue... or not (?) by Krist...
Infinum
 
Building Modern Apps using Android Architecture Components
Building Modern Apps using Android Architecture ComponentsBuilding Modern Apps using Android Architecture Components
Building Modern Apps using Android Architecture Components
Hassan Abid
 
android level 3
android level 3android level 3
android level 3
DevMix
 
Android accessibility for developers and QA
Android accessibility for developers and QAAndroid accessibility for developers and QA
Android accessibility for developers and QA
Ted Drake
 
Android gui framework
Android gui frameworkAndroid gui framework
Android gui framework
Sri Harsha Pamu
 
Answer1)Responsive design is the idea where all the developed pag.pdf
Answer1)Responsive design is the idea where all the developed pag.pdfAnswer1)Responsive design is the idea where all the developed pag.pdf
Answer1)Responsive design is the idea where all the developed pag.pdf
ankitcomputer11
 
Android Development project
Android Development projectAndroid Development project
Android Development project
Minhaj Kazi
 
Android Workshop
Android WorkshopAndroid Workshop
Android Workshop
Junda Ong
 
Android
AndroidAndroid
Android
Ravi Shankar
 
Android
AndroidAndroid
Android
Pranav Ashok
 
What's new in Android Wear 2.0
What's new in Android Wear 2.0What's new in Android Wear 2.0
What's new in Android Wear 2.0
Peter Friese
 
android design pattern
android design patternandroid design pattern
android design pattern
Lucas Xu
 
Android Bootcamp Tanzania:understanding ui in_android
Android Bootcamp Tanzania:understanding ui in_androidAndroid Bootcamp Tanzania:understanding ui in_android
Android Bootcamp Tanzania:understanding ui in_android
Denis Minja
 
Android Tutorials : Basic widgets
Android Tutorials : Basic widgetsAndroid Tutorials : Basic widgets
Android Tutorials : Basic widgets
Prajyot Mainkar
 
Android Study Jams- Day 2(Hands on Experience)
Android Study Jams- Day 2(Hands on Experience)Android Study Jams- Day 2(Hands on Experience)
Android Study Jams- Day 2(Hands on Experience)
GoogleDSC
 
Stmik bandung
Stmik bandungStmik bandung
Stmik bandung
farid savarudin
 
Responsive mobile design in practice
Responsive mobile design in practiceResponsive mobile design in practice
Responsive mobile design in practice
Kirill Grouchnikov
 

Similar to Compose In Practice (20)

Training Session 2 - Day 2
Training Session 2 - Day 2Training Session 2 - Day 2
Training Session 2 - Day 2
 
Fragments: Why, How, What For?
Fragments: Why, How, What For?Fragments: Why, How, What For?
Fragments: Why, How, What For?
 
How to use data binding in android
How to use data binding in androidHow to use data binding in android
How to use data binding in android
 
Infinum Android Talks #14 - Data binding to the rescue... or not (?) by Krist...
Infinum Android Talks #14 - Data binding to the rescue... or not (?) by Krist...Infinum Android Talks #14 - Data binding to the rescue... or not (?) by Krist...
Infinum Android Talks #14 - Data binding to the rescue... or not (?) by Krist...
 
Building Modern Apps using Android Architecture Components
Building Modern Apps using Android Architecture ComponentsBuilding Modern Apps using Android Architecture Components
Building Modern Apps using Android Architecture Components
 
android level 3
android level 3android level 3
android level 3
 
Android accessibility for developers and QA
Android accessibility for developers and QAAndroid accessibility for developers and QA
Android accessibility for developers and QA
 
Android gui framework
Android gui frameworkAndroid gui framework
Android gui framework
 
Answer1)Responsive design is the idea where all the developed pag.pdf
Answer1)Responsive design is the idea where all the developed pag.pdfAnswer1)Responsive design is the idea where all the developed pag.pdf
Answer1)Responsive design is the idea where all the developed pag.pdf
 
Android Development project
Android Development projectAndroid Development project
Android Development project
 
Android Workshop
Android WorkshopAndroid Workshop
Android Workshop
 
Android
AndroidAndroid
Android
 
Android
AndroidAndroid
Android
 
What's new in Android Wear 2.0
What's new in Android Wear 2.0What's new in Android Wear 2.0
What's new in Android Wear 2.0
 
android design pattern
android design patternandroid design pattern
android design pattern
 
Android Bootcamp Tanzania:understanding ui in_android
Android Bootcamp Tanzania:understanding ui in_androidAndroid Bootcamp Tanzania:understanding ui in_android
Android Bootcamp Tanzania:understanding ui in_android
 
Android Tutorials : Basic widgets
Android Tutorials : Basic widgetsAndroid Tutorials : Basic widgets
Android Tutorials : Basic widgets
 
Android Study Jams- Day 2(Hands on Experience)
Android Study Jams- Day 2(Hands on Experience)Android Study Jams- Day 2(Hands on Experience)
Android Study Jams- Day 2(Hands on Experience)
 
Stmik bandung
Stmik bandungStmik bandung
Stmik bandung
 
Responsive mobile design in practice
Responsive mobile design in practiceResponsive mobile design in practice
Responsive mobile design in practice
 

Recently uploaded

APIs for Browser Automation (MoT Meetup 2024)
APIs for Browser Automation (MoT Meetup 2024)APIs for Browser Automation (MoT Meetup 2024)
APIs for Browser Automation (MoT Meetup 2024)
Boni García
 
Orion Context Broker introduction 20240604
Orion Context Broker introduction 20240604Orion Context Broker introduction 20240604
Orion Context Broker introduction 20240604
Fermin Galan
 
Microservice Teams - How the cloud changes the way we work
Microservice Teams - How the cloud changes the way we workMicroservice Teams - How the cloud changes the way we work
Microservice Teams - How the cloud changes the way we work
Sven Peters
 
Oracle Database 19c New Features for DBAs and Developers.pptx
Oracle Database 19c New Features for DBAs and Developers.pptxOracle Database 19c New Features for DBAs and Developers.pptx
Oracle Database 19c New Features for DBAs and Developers.pptx
Remote DBA Services
 
Utilocate provides Smarter, Better, Faster, Safer Locate Ticket Management
Utilocate provides Smarter, Better, Faster, Safer Locate Ticket ManagementUtilocate provides Smarter, Better, Faster, Safer Locate Ticket Management
Utilocate provides Smarter, Better, Faster, Safer Locate Ticket Management
Utilocate
 
Webinar On-Demand: Using Flutter for Embedded
Webinar On-Demand: Using Flutter for EmbeddedWebinar On-Demand: Using Flutter for Embedded
Webinar On-Demand: Using Flutter for Embedded
ICS
 
KuberTENes Birthday Bash Guadalajara - Introducción a Argo CD
KuberTENes Birthday Bash Guadalajara - Introducción a Argo CDKuberTENes Birthday Bash Guadalajara - Introducción a Argo CD
KuberTENes Birthday Bash Guadalajara - Introducción a Argo CD
rodomar2
 
Revolutionizing Visual Effects Mastering AI Face Swaps.pdf
Revolutionizing Visual Effects Mastering AI Face Swaps.pdfRevolutionizing Visual Effects Mastering AI Face Swaps.pdf
Revolutionizing Visual Effects Mastering AI Face Swaps.pdf
Undress Baby
 
Essentials of Automations: The Art of Triggers and Actions in FME
Essentials of Automations: The Art of Triggers and Actions in FMEEssentials of Automations: The Art of Triggers and Actions in FME
Essentials of Automations: The Art of Triggers and Actions in FME
Safe Software
 
Using Xen Hypervisor for Functional Safety
Using Xen Hypervisor for Functional SafetyUsing Xen Hypervisor for Functional Safety
Using Xen Hypervisor for Functional Safety
Ayan Halder
 
Mobile App Development Company In Noida | Drona Infotech
Mobile App Development Company In Noida | Drona InfotechMobile App Development Company In Noida | Drona Infotech
Mobile App Development Company In Noida | Drona Infotech
Drona Infotech
 
Fundamentals of Programming and Language Processors
Fundamentals of Programming and Language ProcessorsFundamentals of Programming and Language Processors
Fundamentals of Programming and Language Processors
Rakesh Kumar R
 
SWEBOK and Education at FUSE Okinawa 2024
SWEBOK and Education at FUSE Okinawa 2024SWEBOK and Education at FUSE Okinawa 2024
SWEBOK and Education at FUSE Okinawa 2024
Hironori Washizaki
 
2024 eCommerceDays Toulouse - Sylius 2.0.pdf
2024 eCommerceDays Toulouse - Sylius 2.0.pdf2024 eCommerceDays Toulouse - Sylius 2.0.pdf
2024 eCommerceDays Toulouse - Sylius 2.0.pdf
Łukasz Chruściel
 
A Study of Variable-Role-based Feature Enrichment in Neural Models of Code
A Study of Variable-Role-based Feature Enrichment in Neural Models of CodeA Study of Variable-Role-based Feature Enrichment in Neural Models of Code
A Study of Variable-Role-based Feature Enrichment in Neural Models of Code
Aftab Hussain
 
Artificia Intellicence and XPath Extension Functions
Artificia Intellicence and XPath Extension FunctionsArtificia Intellicence and XPath Extension Functions
Artificia Intellicence and XPath Extension Functions
Octavian Nadolu
 
Atelier - Innover avec l’IA Générative et les graphes de connaissances
Atelier - Innover avec l’IA Générative et les graphes de connaissancesAtelier - Innover avec l’IA Générative et les graphes de connaissances
Atelier - Innover avec l’IA Générative et les graphes de connaissances
Neo4j
 
socradar-q1-2024-aviation-industry-report.pdf
socradar-q1-2024-aviation-industry-report.pdfsocradar-q1-2024-aviation-industry-report.pdf
socradar-q1-2024-aviation-industry-report.pdf
SOCRadar
 
What is Augmented Reality Image Tracking
What is Augmented Reality Image TrackingWhat is Augmented Reality Image Tracking
What is Augmented Reality Image Tracking
pavan998932
 
Automated software refactoring with OpenRewrite and Generative AI.pptx.pdf
Automated software refactoring with OpenRewrite and Generative AI.pptx.pdfAutomated software refactoring with OpenRewrite and Generative AI.pptx.pdf
Automated software refactoring with OpenRewrite and Generative AI.pptx.pdf
timtebeek1
 

Recently uploaded (20)

APIs for Browser Automation (MoT Meetup 2024)
APIs for Browser Automation (MoT Meetup 2024)APIs for Browser Automation (MoT Meetup 2024)
APIs for Browser Automation (MoT Meetup 2024)
 
Orion Context Broker introduction 20240604
Orion Context Broker introduction 20240604Orion Context Broker introduction 20240604
Orion Context Broker introduction 20240604
 
Microservice Teams - How the cloud changes the way we work
Microservice Teams - How the cloud changes the way we workMicroservice Teams - How the cloud changes the way we work
Microservice Teams - How the cloud changes the way we work
 
Oracle Database 19c New Features for DBAs and Developers.pptx
Oracle Database 19c New Features for DBAs and Developers.pptxOracle Database 19c New Features for DBAs and Developers.pptx
Oracle Database 19c New Features for DBAs and Developers.pptx
 
Utilocate provides Smarter, Better, Faster, Safer Locate Ticket Management
Utilocate provides Smarter, Better, Faster, Safer Locate Ticket ManagementUtilocate provides Smarter, Better, Faster, Safer Locate Ticket Management
Utilocate provides Smarter, Better, Faster, Safer Locate Ticket Management
 
Webinar On-Demand: Using Flutter for Embedded
Webinar On-Demand: Using Flutter for EmbeddedWebinar On-Demand: Using Flutter for Embedded
Webinar On-Demand: Using Flutter for Embedded
 
KuberTENes Birthday Bash Guadalajara - Introducción a Argo CD
KuberTENes Birthday Bash Guadalajara - Introducción a Argo CDKuberTENes Birthday Bash Guadalajara - Introducción a Argo CD
KuberTENes Birthday Bash Guadalajara - Introducción a Argo CD
 
Revolutionizing Visual Effects Mastering AI Face Swaps.pdf
Revolutionizing Visual Effects Mastering AI Face Swaps.pdfRevolutionizing Visual Effects Mastering AI Face Swaps.pdf
Revolutionizing Visual Effects Mastering AI Face Swaps.pdf
 
Essentials of Automations: The Art of Triggers and Actions in FME
Essentials of Automations: The Art of Triggers and Actions in FMEEssentials of Automations: The Art of Triggers and Actions in FME
Essentials of Automations: The Art of Triggers and Actions in FME
 
Using Xen Hypervisor for Functional Safety
Using Xen Hypervisor for Functional SafetyUsing Xen Hypervisor for Functional Safety
Using Xen Hypervisor for Functional Safety
 
Mobile App Development Company In Noida | Drona Infotech
Mobile App Development Company In Noida | Drona InfotechMobile App Development Company In Noida | Drona Infotech
Mobile App Development Company In Noida | Drona Infotech
 
Fundamentals of Programming and Language Processors
Fundamentals of Programming and Language ProcessorsFundamentals of Programming and Language Processors
Fundamentals of Programming and Language Processors
 
SWEBOK and Education at FUSE Okinawa 2024
SWEBOK and Education at FUSE Okinawa 2024SWEBOK and Education at FUSE Okinawa 2024
SWEBOK and Education at FUSE Okinawa 2024
 
2024 eCommerceDays Toulouse - Sylius 2.0.pdf
2024 eCommerceDays Toulouse - Sylius 2.0.pdf2024 eCommerceDays Toulouse - Sylius 2.0.pdf
2024 eCommerceDays Toulouse - Sylius 2.0.pdf
 
A Study of Variable-Role-based Feature Enrichment in Neural Models of Code
A Study of Variable-Role-based Feature Enrichment in Neural Models of CodeA Study of Variable-Role-based Feature Enrichment in Neural Models of Code
A Study of Variable-Role-based Feature Enrichment in Neural Models of Code
 
Artificia Intellicence and XPath Extension Functions
Artificia Intellicence and XPath Extension FunctionsArtificia Intellicence and XPath Extension Functions
Artificia Intellicence and XPath Extension Functions
 
Atelier - Innover avec l’IA Générative et les graphes de connaissances
Atelier - Innover avec l’IA Générative et les graphes de connaissancesAtelier - Innover avec l’IA Générative et les graphes de connaissances
Atelier - Innover avec l’IA Générative et les graphes de connaissances
 
socradar-q1-2024-aviation-industry-report.pdf
socradar-q1-2024-aviation-industry-report.pdfsocradar-q1-2024-aviation-industry-report.pdf
socradar-q1-2024-aviation-industry-report.pdf
 
What is Augmented Reality Image Tracking
What is Augmented Reality Image TrackingWhat is Augmented Reality Image Tracking
What is Augmented Reality Image Tracking
 
Automated software refactoring with OpenRewrite and Generative AI.pptx.pdf
Automated software refactoring with OpenRewrite and Generative AI.pptx.pdfAutomated software refactoring with OpenRewrite and Generative AI.pptx.pdf
Automated software refactoring with OpenRewrite and Generative AI.pptx.pdf
 

Compose In Practice

  • 1. © Instil Software 2020 Compose Part 2 (the practice) Belfast Google Dev Group September 2022
  • 3. – Short history tour of Android UI Development. – Examples of using Jetpack Compose in across multiple apps. – Focus on Navigation, Accessibility & Testing. – Sneak peak at an upcoming Instil app! AGENDA
  • 5. The classic - findViewById XML layout <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout> id used by Activity
  • 6. The classic - findViewById Activity private lateinit var textView: TextView override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) textView = findViewById<TextView?>(R.id.textView).apply { text = "Hello GDE" textSize = 42f setTextColor(Color.CYAN) } }
  • 7.
  • 10. class DemoActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val binding = DataBindingUtil.setContentView<ActivityDemoBinding> (this,R.layout.activity_demo) binding.vm = DemoViewModel() } } Databinding Activity XML layout file setting vm on XML
  • 11. class DemoViewModel { val text = ObservableField("Data binding works!") } ViewModel
  • 12. The shiny new thing, Jetpack Compose!
  • 14. … but starting from scratch?
  • 16.
  • 17.
  • 18.
  • 19. WIP
  • 20.
  • 22. Previews that work! @Composable @Preview(uiMode = UI_MODE_NIGHT_YES) fun CarouselScreenDarkPreview() { EstiMateTheme { CarouselScreen( {}, { CardSelection.Nuclear }, CardSelection.Nuclear ) } }
  • 23. Card( backgroundColor = instilGreen, shape = RoundedCornerShape(size = 16.dp), elevation = 8.dp, modifier = modifier .padding(8.dp) .width(120.dp) .height(150.dp) .clickable { onSelected(selection) }, ) { CardContent( selection = selection, fontSize = fontSize ) } EstiMateCard default modifier material component
  • 24. Box(contentAlignment = Alignment.Center) { when (selection) { is CoffeeBreak -> CoffeeImage() is Nuclear -> NuclearImage() else -> SelectionText(selection, fontSize) } } CardContent
  • 26. open fun getPlayers(teamName: String): Flow<List<Player>> = callbackFlow { firebaseService.observePlayersIn(teamName) { players -> trySend(players) } awaitClose { channel.close() } } PlayerService calling to Firebase posting a fresh list of players
  • 27. val players: StateFlow<List<Player>> = playerService.getPlayers() .stateIn( scope = viewModelScope, started = SharingStarted.Lazily, initialValue = emptyList() ) ViewModel called to PlayerService cancels the work once vm cleared
  • 28. val votedPlayers: List<Player> by viewModel.players.collectAsState() ... Composable calling to ViewModel LazyColumn(modifier = Modifier.fillMaxWidth()) { items(votedPlayers.size) { index -> UserVoteItem(votedPlayers[index]) } } composable per list item listOf( Player(CardSelection.Five, "Kelvin"), Player(CardSelection.Three, "Garth") )
  • 29.
  • 30. – Started in 2016. – MVP pattern through use of interfaces. – Jetpack Compose introduced as we took ownership of development. – Shipped 2 new UI driven features with Jetpack Compose. Vypr Android App
  • 32.
  • 33. Using Jetpack Compose in XML Views <androidx.compose.ui.platform.ComposeView android:id="@+id/compose_view" android:layout_width="match_parent" android:layout_height="match_parent" /> reference id
  • 34. Using Jetpack Compose in XML Views private var _binding: FragmentSteersBinding? = null private val binding get() = _binding!! override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { _binding = FragmentSteersBinding.inflate(inflater, container, false) val view = binding.root binding.composeView.apply { setViewCompositionStrategy(DisposeOnViewTreeLifecycleDestroyed) setContent { VyprTheme { SteersListView() } } } return view } compose_view xml element our new composable
  • 35. Using XML views in Jetpack Compose AndroidView( modifier = Modifier.fillMaxSize(), factory = { context -> AspectRatioImageView(context).apply { load(steer.previewImageUrl) setOnClickListener { onSteerClicked(steer) } } } ) legacy view with lots of scary
  • 36.
  • 37. – RecyclerView & adapter complexity removed. – Jetpack Compose views driven by lifecycle aware ViewModel. – More testable implementation. – We fixed the bug! Steers List Interop example
  • 43. – NavController - central API for stateful navigation. – NavHost - links NavController with a navigation graph. – Each Composable screen is known as a route. Jetpack Navigation Now supporting Compose
  • 44. Compose Navigation! NavHost(navController = navController, startDestination = "profile") { composable("profile") { Profile(/*...*/) } composable("friendslist") { FriendsList(/*...*/) } /*...*/ }
  • 45. Navigating to another Route navController.navigate("friendslist")
  • 46. Compose Navigation With Arguments NavHost(startDestination = "profile/{userId}") { ... composable( "profile/{userId}", arguments = listOf(navArgument("userId") { type = NavType.StringType }) ) {...} } ... navController.navigate("profile/user1234")
  • 47.
  • 49. A KSP library that processes annotations and generates code that uses Official Jetpack Compose Navigation under the hood. It hides the complex, non-type-safe and boilerplate code you would have to write otherwise. Rafael Costa github.com/raamcosta/compose-destinations
  • 50. Adding a Destination @Destination(start = true) @Composable fun LoginRoute( destinationsNavigator: DestinationsNavigator ) { LoginScreen( ... ) } tag composable for generation provided for nav
  • 51. Generated NavGraph object NavGraphs { val root = NavGraph( route = "root", startRoute = LoginRouteDestination, destinations = listOf( CarouselRouteDestination, LoginRouteDestination, ResultsRouteDestination, SelectionRouteDestination ) ) } each annotated composable labelled start
  • 52. Type safe navigation onLoginClicked = { destinationsNavigator.navigate(SelectionRouteDestination) },
  • 53. Type safe navigation with args onCardSelected = { cardSelection -> destinationsNavigator.navigate( CarouselRouteDestination(selection = cardSelection) ) })
  • 54. Custom Serializer @NavTypeSerializer class CardSelectionSerializer : DestinationsNavTypeSerializer<CardSelection> { override fun fromRouteString(routeStr: String): CardSelection { return CardSelection.fromString(routeStr) } override fun toRouteString(value: CardSelection): String { return value.name } }
  • 55. Under the hood object LoginRouteDestination : DirectionDestination { operator fun invoke() = this @get:RestrictTo(RestrictTo.Scope.SUBCLASSES) override val baseRoute = "login_route" override val route = baseRoute @Composable override fun DestinationScope<Unit>.Content( dependenciesContainerBuilder: @Composable DependenciesContainerBuilder<Unit>.() -> Unit ) { LoginRoute( destinationsNavigator = destinationsNavigator ) } } our composable
  • 58. “Mobile accessibility” refers to making websites and applications more accessible to people with disabilities when they are using mobile phones and other devices. Shawn Lawton Henry w3.org/WAI/standards-guidelines/mobile/
  • 59. Android Accessibility – Switch Access: interact with switches instead of the touchscreen. – BrailleBack: refreshable Braille display to an Android device over Bluetooth. – Voice Access: control an Android device with spoken commands. – TalkBack: spoken feedback for UI interactions. What options are baked into the OS?
  • 61. An essential tool for every Android team
  • 62.
  • 64. Play Store Testing - Example Issue
  • 65. Screen Reader issue confirmed no context of dialog
  • 66. Fixing the Dialog val accountDeletionDialogAccessibilityLabel = stringResource( id = R.string.accessibility_account_deletion_delete_dialog ) AlertDialog( ... modifier = Modifier.semantics(mergeDescendants = true) { contentDescription = accountDeletionDialogAccessibilityLabel } )
  • 69. Screen Sizes & Resolution
  • 70. UI Mockups – Discuss what the UI toolkit can do when size is constrained. Compose is good at scaling text! – Agree how to handle view scaling. – Agree copy for accessibility labelling. – Collaborate with designers & product owners.
  • 72. Compose Semantics Semantics, as the name implies, give meaning to a piece of UI. In this context, a "piece of UI" (or element) can mean anything from a single composable to a full screen. The semantics tree is generated alongside the UI hierarchy, and describes it.
  • 73. Example Button Button( modifier = Modifier.semantics { contentDescription = "Add to favorites" } ) individual ui elements make up a button easier to find
  • 74. Test Setup @get:Rule val composeTestRule = createAndroidComposeRule<VyPopsActivity>() @Before fun beforeEachTest() { composeTestRule.setContent { VyprTheme { VyPopsLandingScreen(EmptyDestinationsNavigator) } } }
  • 75. Finders Select one or more elements (nodes) to assert or act on composeTestRule .onNodeWithContentDescription("Close Button") composeTestRule .onNodeWithText("What happens next")
  • 76. Finders - Debug Logging Node #1 at (l=0.0, t=54.0, r=720.0, b=1436.0)px |-Node #2 at (l=70.0, t=54.0, r=650.0, b=1436.0)px ContentDescription = '[VyPops Permissions Page]' |-Node #3 at (l=70.0, t=75.0, r=112.0, b=117.0)px | Role = 'Button' | Focused = 'false' | ContentDescription = '[Close Button]' | Actions = [OnClick] | MergeDescendants = 'true' |-Node #6 at (l=229.0, t=194.0, r=492.0, b=303.0)px | ContentDescription = '[Vypr Logo]' | Role = 'Image' |-Node #7 at (l=91.0, t=687.0, r=133.0, b=729.0)px | ContentDescription = '[Record Audio Tick]' | Role = 'Image' |-Node #8 at (l=147.0, t=684.0, r=615.0, b=731.0)px | Text = '[Microphone access granted]' | Actions = [GetTextLayoutResult] |-Node #9 at (l=125.0, t=762.0, r=167.0, b=804.0)px | ContentDescription = '[Camera Tick]' | Role = 'Image' |-Node #10 at (l=181.0, t=759.0, r=582.0, b=806.0)px | Text = '[Camera access granted]' | Actions = [GetTextLayoutResult] |-Node #11 at (l=84.0, t=1275.0, r=636.0, b=1366.0)px Text = '[VyPops needs access to both your camera and microphone.]' Actions = [GetTextLayoutResult] Node #1 at (l=0.0, t=54.0, r=720.0, b=1436.0)px |-Node #2 at (l=70.0, t=54.0, r=650.0, b=1436.0)px ContentDescription = '[VyPops Permissions Page]' |-Node #3 at (l=70.0, t=75.0, r=112.0, b=117.0)px | Role = 'Button' | Focused = 'false' | Actions = [OnClick] | MergeDescendants = 'true' | |-Node #5 at (l=70.0, t=75.0, r=112.0, b=117.0)px | ContentDescription = '[Close Button]' | Role = 'Image' |-Node #6 at (l=229.0, t=194.0, r=492.0, b=303.0)px | ContentDescription = '[Vypr Logo]' | Role = 'Image' |-Node #7 at (l=91.0, t=687.0, r=133.0, b=729.0)px | ContentDescription = '[Record Audio Tick]' | Role = 'Image' |-Node #8 at (l=147.0, t=684.0, r=615.0, b=731.0)px | Text = '[Microphone access granted]' | Actions = [GetTextLayoutResult] |-Node #9 at (l=125.0, t=762.0, r=167.0, b=804.0)px | ContentDescription = '[Camera Tick]' | Role = 'Image' |-Node #10 at (l=181.0, t=759.0, r=582.0, b=806.0)px | Text = '[Camera access granted]' | Actions = [GetTextLayoutResult] |-Node #11 at (l=84.0, t=1275.0, r=636.0, b=1366.0)px Text = '[VyPops needs access to both your camera and microphone.]' Actions = [GetTextLayoutResult]
  • 77. Assertions Verify elements exist or have certain attributes composeTestRule .onNodeWithContentDescription("Login Button") .assertIsEnabled() composeTestRule .onNodeWithText("What happens next") .assertIsDisplayed()
  • 78. Simulate user input or gestures Actions composeTestRule .onNodeWithContentDescription("Close Button") .performClick() ... .performTouchInput { swipeLeft() }
  • 80. Shot is a Gradle plugin and a core android library thought to run screenshot tests for Android. Pedro Gómez github.com/pedrovgs/Shot
  • 81. ./gradlew executeScreenshotTests -Precord Example Test @get:Rule val composeRule = createAndroidComposeRule<AccountDeletionActivity>() @Test fun accountDeletedScreenBodyScreenshot() { composeRule.setContent { AccountDeletedScreenBody() } compareScreenshot(composeRule) }
  • 84.
  • 85.
  • 86. Introducing Compose Phased approach – Do you have an existing app with custom UI components? – Recreate them in Compose! – Provide a foundation to educate your team. – Define standards & best practices.
  • 89. – We’ve adopted Jetpack Compose for all new Android projects. – Excellent official documentation & codelabs available. – Good tooling and a growing list of third-party libraries available. – Recommend new starts prioritise Jetpack Compose over XML. Conclusions
  • 90. EstiMate for Android is coming soon™