Blog Infos
Author
Published
Topics
Published

 

Photo by Arno Senoner on Unsplash

 

An important and quickly expanding subset of active Android smartphones have large screens. Large-screen Android devices are used on more than 270 million different devices. Tablets, foldable gadgets, and Chrome OS gadgets are among them. Make your app’s user interface (UI) responsive to a variety of devices to reach this expanding group of Android users.

You will pick up knowledge about:
  • APIs and tools to build great user experiences for large-screen Android devices.
  • Designing adaptive apps for different screen sizes, orientations, and form factors.
  • Google Play updates for large-screen devices.
  • Testing your app layouts for large screens.

Note: This lesson assumes you have prior knowledge of Kotlin and Jetpack Compose in Android development.

Designing Adaptive Apps

A wonderful user experience is offered by responsive apps on a variety of screen sizes and form factors. They offer resizable settings including multi-window mode and various screen orientations.

Material Design 3 offers canonical layouts to aid in the creation of adaptive layouts. The creation of responsive layouts for large screens uses canonical layouts as a model. They consist of:

  • List-detail view: A list of items is placed on the left in a list-detail view. You display an item’s details on the right side.
  • Supporting panel: Focus and support areas make up a layout. The primary content is displayed in the focus area. It takes up around two-thirds of the display space. The remaining screen real estate is taken up by the accompanying panel, which displays extra content like document comments. It is positioned in the back third of an increased width or the bottom third of an extended height.
  • Feed: News or social content apps frequently use feed layouts. When the width is not compact, for instance, use a different layout manager like GridLayoutManager with a RecyclerView.

You cannot choose the app layouts to utilize based on the type of device the user is using. On tablets, for instance, an app may be running in multi-window mode and sharing the screen with another app. Or there might be multiple physical screens on a foldable gadget. Instead, use the Jetpack WindowManager library to base choices on the actual area of the screen that is allotted.

Study of Window Size Classes

In order to help you create responsive and adaptive layouts, window size classes serve as viewport breakpoints. They categorize the available screen real estate for your app as a tiny, medium, or enlarged.

Available width and height are divided into different categories. Because vertical scrolling is prevalent across devices, the available width is more significant than the available height. The following categories apply to the available width:

  • Compact width: The device width is relatively small — less than 600dp. These include phones that are held in portrait mode and folding foldable.
  • Medium width: The screen’s dimensions are greater than 600dp. Tablets and huge unfolded foldable in portrait orientation are examples of medium-width devices.
  • Expanded width: This category includes tablets and huge, unfolded, landscape-oriented foldable. They have a width of more than 840dp.

You’ll use the material3-window-size-class library to get the window size class of a device. The library calculates the window size class using current window metrics.

Open build.gradle(app)add the library and sync.

implementation "androidx.compose.material3:material3-window-size-class:1.0.0-alpha14"

add the below code to your MainActivity.kt

val windowSizeClass = calculateWindowSizeClass(activity = this)

The code above returns the window size class for the provided activity. calculates WindowSizeClass for the provided activity. The method returns a new WindowSizeClass one during screen rotation or window resizing. The app recomposes the UI with the new window size class.

Add any missing imports by pressing Alt-Enter on the PC.

You may see an error squiggly line. This is because the library is still experimental. To fix the error, add the following before onCreate() and import the corresponding package:

@OptIn(ExperimentalMaterial3WindowSizeClassApi::class)

Next, you’ll supply the composable with windowSizeClass. Later, you’ll use this knowledge to choose the app layouts.

windowSizeClass = windowSizeClass.widthSizeClass,

You’ll also take device fold posture into account when updating the app to accommodate changes in screen sizes.

Observing the Device’s Folded Posture

A foldable gadget has a variety of states and positions. It may be folded or unfolded and can be oriented either in portrait or landscape. It can be holding a book or a tabletop. Various folding postures are supported by an adaptive design.

The WindowLayoutInfo class of the Jetpack WindowManager library offers the following details regarding foldable displays:

  • state: The folded state is described by this. When the device is entirely opened, or HALF OPENED, its value is FLAT.
  • orientation: The hinge’s orientation. It might be vertical or horizontal.
  • occlusionType: When the hinge conceals a portion of the display, the value is FULL. If not, NONE is the value.
  • isSeparating: When the hinge produces two logical displays, it is true.
The following postures are defined by DevicePosture
  • Normal posture: Whether a device is fully folded or opened, a normal posture is either.
  • Book posture: The device’s fold state is HALF OPENED and it is in portrait orientation.
  • Separating posture: The gadget is in an open position with a FLAT fold condition. It is comparable to the situation where a physical hinge causes occlusion type to be FULL in a device’s position. Don’t put anything that can be touched or seen underneath the hinge.
Device Fold Posture Analysis
// 1
val devicePostureFlow = WindowInfoTracker.getOrCreate(this).windowLayoutInfo(this)
  .flowWithLifecycle(this.lifecycle)
  // 2
  .map { layoutInfo ->
    val foldingFeature =
      layoutInfo.displayFeatures
        .filterIsInstance()
        .firstOrNull()
    when {
      isBookPosture(foldingFeature) ->
        DevicePosture.BookPosture(foldingFeature.bounds)

      isSeparating(foldingFeature) ->
        DevicePosture.Separating(foldingFeature.bounds, foldingFeature.orientation)

      else -> DevicePosture.NormalPosture
    }
  }
  .stateIn(
    scope = lifecycleScope,
    started = SharingStarted.Eagerly,
    initialValue = DevicePosture.NormalPosture
  )

 

Job Offers

Job Offers

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

, ,

Migrating to Jetpack Compose – an interop love story

Most of you are familiar with Jetpack Compose and its benefits. If you’re able to start anew and create a Compose-only app, you’re on the right track. But this talk might not be for you…
Watch Video

Migrating to Jetpack Compose - an interop love story

Simona Milanovic
Android DevRel Engineer for Jetpack Compose
Google

Migrating to Jetpack Compose - an interop love story

Simona Milanovic
Android DevRel Engin ...
Google

Migrating to Jetpack Compose - an interop love story

Simona Milanovic
Android DevRel Engineer f ...
Google

Jobs

To stop Android Studio from complaining, you should also include the following imports:
import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope
import androidx.window.layout.FoldingFeature
import androidx.window.layout.WindowInfoTracker
import com.yourcompany.android.craftynotebook.presentation.util.DevicePosture
import com.yourcompany.android.craftynotebook.presentation.util.isBookPosture
import com.yourcompany.android.craftynotebook.presentation.util.isSeparating
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn

Kotlin Flows are used in the code above to interact with WindowLayoutInfo data collection.

  1. windowLayoutInfo(activity: Activity) returns Flow, which is a device’s display information. Every time the display information is altered, the procedure produces WindowLayoutInfo.
  2. To establish the device fold posture, it employs the map operator and the display data supplied by windowLayoutInfo(activity: Activity).

You will then see the device’s posture and composition status. MainActivity.kt contains

val devicePosture = devicePostureFlow.collectAsState().value
Selecting the Correct Navigation Type

Different sorts of navigational elements that adjust to changes in display size are included in responsive user interfaces.

The material library offers navigational elements such as drawer, rail, and bottom navigation. Depending on the device’s window size class, you’ll implement the navigation that is most appropriate:

  • Bottom navigation: Navigation at the bottom: For small window sizes, bottom navigation is ideal.
  • Navigation rail: Use the navigation rail for medium-sized screens.
  • Navigation drawer: This navigation drawer would work well with tablets and other large-screen devices. Navigation drawers come in two varieties: modal and permanent. Use a modal navigation drawer for compact to medium sizes because it can be enlarged as an overlay on the content or concealed. For fixed navigation on large screens, such as those found in tablets and Chrome OS devices, use a persistent navigation drawer.
Use the following and import the package for NavigationType:
// 1
val navigationType: NavigationType
// 2
when (windowSizeClass) {
  WindowWidthSizeClass.Compact -> {
    navigationType = NavigationType.BOTTOM_NAVIGATION
    // TODO 13
  }
  WindowWidthSizeClass.Medium -> {
    navigationType = NavigationType.NAVIGATION_RAIL
    // TODO 14
  }
  WindowWidthSizeClass.Expanded -> {
    // 3
    navigationType = if (devicePosture is DevicePosture.BookPosture) {
      NavigationType.NAVIGATION_RAIL
    } else {
      NavigationType.PERMANENT_NAVIGATION_DRAWER
    }
    // TODO 15
  }
  else -> {
    navigationType = NavigationType.BOTTOM_NAVIGATION
    // TODO 16
  }
}

What the code above does is:

  1. declares the variable for navigationType.
  2. Depending on the window size class, it initializes navigationType with the appropriate value using a switch statement.
  3. handles the folded position to prevent inserting items or performing touching actions near the hinge. Use a navigation rail to split information around the hinge when a device is in BookPosture. Use a permanent navigation drawer for large desktops or tablets.

The navigationType argument is then passed to the NoteNavigationWrapperUi() composable method.

navigationType = navigationType,

Note: The application is now aware of the navigational approaches to use for various window size classes and device fold postures. To ensure optimal engagement and reachability, you’ll implement new navigation strategies next.

Putting in place Responsive Navigation

Add the following code to your main class.

if (navigationType == NavigationType.PERMANENT_NAVIGATION_DRAWER) {
  PermanentNavigationDrawer(drawerContent = {
    NavigationDrawerContent(
      navController = navController
    )
  }) {
    NoteAppContent(
      navigationType = navigationType,
      contentType = contentType,
      modifier = modifier,
      navController = navController,
      notesViewModel = notesViewModel
    )
  }
} else {
  ModalNavigationDrawer(
    drawerContent = {
      NavigationDrawerContent(
        navController = navController,
        onDrawerClicked = {
          scope.launch {
            drawerState.close()
          }
        }
      )
    },
    drawerState = drawerState
  ) {
    NoteAppContent(
      navigationType = navigationType,
      contentType = contentType,
      modifier = modifier,
      navController = navController,
      notesViewModel = notesViewModel,
      onDrawerClicked = {
        scope.launch {
          drawerState.open()
        }
      }
    )
  }
}

As usual, there are a few additional imports you must include:

import kotlinx.coroutines.launch
import androidx.compose.material3.*

The notes UI is housed in the navigation drawer. Depending on the value of navigationType, the aforementioned code wraps the composable call with a permanent or modal navigation drawer.

Add the below code in your class

Row(modifier = Modifier.fillMaxSize()) {
  AnimatedVisibility(visible = navigationType == NavigationType.NAVIGATION_RAIL) {
    NoteNavigationRail(
      onDrawerClicked = onDrawerClicked,
      navController = navController
    )
  }
  Column(
    modifier = modifier.fillMaxSize()
  ) {
    NoteNavHost(
      modifier = modifier.weight(1f),
      contentType = contentType,
      navController = navController,
      notesViewModel = notesViewModel
    )
    AnimatedVisibility(visible = navigationType == NavigationType.BOTTOM_NAVIGATION) {
      NoteBottomNavigationBar(navController = navController)
    }
  }
}

Additionally, provide the following imports to please Android Studio:

The positioning of the bottom navigation or navigation rail is determined by navigationType in the code above. The AnimatedVisibility() composable was used to enclose both the bottom navigation and the navigation rail. Depending on the navigationType, this animates the entry and exit visibility of each navigation.

Build and run.

Thank you for taking the time to read this article. If you found this post to be useful and interesting, please clap and recommend it.

If I got something wrong, mention it in the comments. I would love to improve

Connect with me on GitHuband LinkedIn.

This article was originally published on proandroiddev.com

YOU MAY BE INTERESTED IN

YOU MAY BE INTERESTED IN

blog
It’s one of the common UX across apps to provide swipe to dismiss so…
READ MORE
blog
In this part of our series on introducing Jetpack Compose into an existing project,…
READ MORE
blog
In the world of Jetpack Compose, where designing reusable and customizable UI components is…
READ MORE
blog

How to animate BottomSheet content using Jetpack Compose

Early this year I started a new pet project for listening to random radio…
READ MORE

Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.

Menu