Fiori Horizon Object Cards¶
Design¶
Fiori Horizon Object Cards, based on the Material Card View, may consist of as little as a single Title, or as much as a section comprised of a Card Header, Content Description, and two Action Buttons. Object Cards support both Light mode and Dark mode, as determined by the device's settings.
![]() |
![]() |
---|---|
![]() |
![]() |
---|---|
Development¶
Fiori Horizon Object Cards are implemented entirely using Jetpack Compose, including a Composable
FioriHorizonTheme
. To understand the fundamentals of Jetpack Compose, refer to the Android Developer's documentation.
The fiori-horizon-cards
module provides four UI components for application developers to use out of the box:
ObjectCard
: a UI component to display a single Object Card.ObjectCardRow
: a UI component to display a series of Object Cards in a horizontally scrollable row.ObjectCardColumn
: a UI component to display a series of Object Cards in a vertically scrollable column.ObjectCardGrid
: a UI component to display a series of Object Cards in a vertically scrollable grid.
The latter three components are all responsive to different screen sizes.
Usage in an Application¶
This section describes how to use the Object Card components in an application. A sample Android application is bundled in this repository to demonstrate the usage of the above components on various screens.
![]() |
![]() |
---|---|
1. How to Create an ObjectCard
¶
ObjectCard
is a Composable
function that takes two parameters:
- An
ObjectCardData
that specifies the data to be displayed on the card - A
Modifier
that is used to define the height and width of the card and/or aTestTag
needed for Unit Tests. A defaultobjectCardModifier
is predefined to set the minimum height and width requirements.
@Composable
fun ObjectCard(
data: ObjectCardData,
modifier: Modifier = objectCardModifier
) {
val uiState = rememberObjectCardUiState(data = data)
ObjectCardContent(
darkTheme = isSystemInDarkTheme(),
uiState = uiState,
modifier = modifier,
cardClickable = data.cardClickable,
onCardClick = data.onCardClick?:{},
)
}
An object of the ObjectCardData
has to be passed in from the application in order to create an ObjectCard
.
@Parcelize
data class ObjectCardData(
val title: String,
val subtitle: String? = null,
val footnote: String? = null,
val statusButton: Pair<Boolean, String>? = null,
val imageThumbnail: @RawValue ImageThumbnail? = null,
val contentDesc: String? = null,
val primaryActionButton: @RawValue ActionButton? = null,
val secondaryActionButton: @RawValue ActionButton? = null,
val menuItems: List<Pair<String, ()->Unit>>? = listOf(),
val cardClickable: Boolean = true,
val onCardClick: (()->Unit)? = null,
val cardBackgroundColor: @RawValue Color? = null,
val cardWidth: Int? = null,
) : Parcelable
Creating an ObjectCard
can be as simple as the following (if there is no need to provide a custom Modifier
):
FioriHorizonTheme(darkTheme = false) {
val objectCardData = remember { generateObjectCardData()}
ObjectCard (
data = objectCardData
)
}
You can customize the ObjectCard
's style, including Action Button styles and card background color, by supplying new values in the ObjectCardData
. For example, to change the background color of the ObjectCard
to light gray:
val objectCardFullHeaderOnlyCustomColor = ObjectCardData(
title = TITLE,
subtitle = SUBTITLE,
footnote = FOOTNOTE,
statusButton = STATUSBUTTON,
imageThumbnail = IMAGETHUMBNAIL,
menuItems = MENUITEMS,
cardBackgroundColor = Color.LightGray
)
When this card is drawn on the screen, it will always have a light gray background.
2. How to Create an ObjectCardRow
, ObjectCardColumn
, or ObjectCardGrid
¶
In a lot of use cases, an application won't display a single ObjectCard
. Instead, it will display a series of Object Cards in a certain layout that could be a horizontal row, a vertical column, or a grid.
All three layouts are defined similarly as a Composable
function in the fiorinextcard
module, even though the implementation details differ from one another. In order to create one of these ObjectCard
series layouts, an application only needs to know the signatures of these functions.
@Composable
fun ObjectCardRow(
cardList: List<ObjectCardData>,
screenType: Enum<ScreenType>,
modifier: Modifier = Modifier
){
...
}
@Composable
fun ObjectCardColumn(
cardList: List<ObjectCardData>,
screenType: Enum<ScreenType>,
modifier: Modifier = Modifier
){
...
}
@Composable
fun ObjectCardGrid(
cardList: List<ObjectCardData>,
screenType: Enum<ScreenType>,
modifier: Modifier = Modifier
){
...
}
Then, to create one of the Object Card layouts, an application should pass in a list of ObjectCardData
objects as well as an Enum
value for ScreenType
. For example:
FioriHorizonTheme {
var objectCardData = remember { generateObjectCardData()}
ObjectCardRow(
cardList = listOf(objectCardData, objectCardData, objectCardData),
screenType = ScreenType.SMALL
)
}
Within the fiori-horizon-cards
module, a utility Composable
function is provided for any application to detect the ScreenType
based on the Android Developers' recommendation documented in Support different screen sizes.
@Composable
fun getScreenType(): ScreenType {
val screenWidth =
LocalContext.current.resources.displayMetrics.widthPixels.dp / LocalDensity.current.density
return when {
screenWidth < 600.dp -> ScreenType.SMALL
screenWidth >= 600.dp && screenWidth < 840.dp -> ScreenType.MEDIUM
screenWidth > 840.dp -> ScreenType.LARGE
else -> ScreenType.SMALL
}
}
3. How to Use These Composable
Components in a Non-Compose, View-Based Application¶
Jetpack Compose is still fairly new. Many applications are still built on the traditional view-based UI. Using a Composable
component in a view-based application consists of two steps:
-
In the XML layout, define the
Composable
component as aComposeView
. For example in the Fiori Next Card Demo app, in the fragment_view_based_object_card.xml, aComposeView
is defined for displaying anObjectCardRow
.<androidx.compose.ui.platform.ComposeView android:id="@+id/compose_view_alerts" android:layout_width="match_parent" android:layout_height="wrap_content" app:layout_constraintTop_toBottomOf="@id/alert_title"/>
-
In the
Fragment
class, inside theonCreateView
function, the actual Compose view can be added to the view tree programmatically while the rest of theFragment
class remains intact.override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { _binding = FragmentViewBasedObjectCardBinding.inflate(inflater, container, false) val view = binding.root.rootView val screenType = getScreenType(requireContext()) view.compose_view_alerts.apply { setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) setContent { // In Compose world FioriHorizonTheme(darkTheme = isSystemInDarkTheme()) { ObjectCardRow(objectCardList = objectCardList6, screenType = screenType) } } } ... return view }