<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: KTS Studio</title>
    <description>The latest articles on DEV Community by KTS Studio (@ktssolutions).</description>
    <link>https://dev.to/ktssolutions</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1035771%2F6c085d59-38b0-4624-ae88-ee1c13a9dd36.png</url>
      <title>DEV Community: KTS Studio</title>
      <link>https://dev.to/ktssolutions</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ktssolutions"/>
    <language>en</language>
    <item>
      <title>How to Add OAuth in iOS in Half an Hour</title>
      <dc:creator>KTS Studio</dc:creator>
      <pubDate>Wed, 08 Mar 2023 01:00:42 +0000</pubDate>
      <link>https://dev.to/ktssolutions/how-to-add-oauth-in-ios-in-half-an-hour-5dgg</link>
      <guid>https://dev.to/ktssolutions/how-to-add-oauth-in-ios-in-half-an-hour-5dgg</guid>
      <description>&lt;p&gt;Hello! &lt;a href="https://www.kts.solutions/"&gt;Our&lt;/a&gt; iOS Developer Lena wrote an article about adding Oath to iOS in half &lt;/p&gt;

&lt;p&gt;In most cases, a mobile application should be able to authorize users for data access, and quite often this is done with third-party services. In these cases, &lt;em&gt;OAuth2.0 is used&lt;/em&gt;. It is a protocol that allows to log into another service without the entering your login and password to the application and giving a restricted set of accesses to the service resources.&lt;/p&gt;

&lt;p&gt;Mobile applications use &lt;em&gt;Authorization Code Flow with Proof Key for Code Exchange (PKCE)&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;Today, we’ll consider implementation of the OAuth authorization with &lt;a href="https://github.com/openid/AppAuth-iOS"&gt;AppAuth-iOS&lt;/a&gt;. It is one of the most popular and easy-to-use libraries. The code from the article is available at &lt;a href="https://github.com/elenakacharmina/ios-oauth-example"&gt;Github&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In this article you will learn about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Basic configuration&lt;/li&gt;
&lt;li&gt;Authorization in the third-party service and saving the access token for further use&lt;/li&gt;
&lt;li&gt;Access token update&lt;/li&gt;
&lt;li&gt;Logout&lt;/li&gt;
&lt;li&gt;Conclusions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Basic configuration&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We’ve chosen OAuth Github as a third-party service. The basic configuration is similar to that of the android application from the previous article: first, &lt;a href="https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/creating-an-oauth-app"&gt;let’s register the OAuth application in Github&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;When registering, set CALLBACK_URL for your application in the service. This is the URL to redirect to after authorization, and to be intercepted by your application.&lt;/p&gt;

&lt;p&gt;We’ll use ru.kts.oauth://github.com/callback as CALLBACK_URL. Don’t forget to use the &lt;em&gt;ru.kts.oauth&lt;/em&gt; custom scheme, so that only your application could intercept the redirect.&lt;/p&gt;

&lt;p&gt;After registering the OAuth application in Github, you should have &lt;em&gt;client_id&lt;/em&gt; and &lt;em&gt;client_secret&lt;/em&gt; you have to generate. Save them.&lt;/p&gt;

&lt;p&gt;Now you need to understand, what URL to go to in order to get authorized in the Github web-page, and what URL is to be used to exchange the code for the token. You can find the answer in the &lt;a href="https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps"&gt;Github OAuth documentation&lt;/a&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;URL for authorization: &lt;a href="https://github.com/login/oauth/authorize"&gt;https://github.com/login/oauth/authorize&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;URL for token exchange: &lt;a href="https://github.com/login/oauth/access_token"&gt;https://github.com/login/oauth/access_token&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To get authorized, you have to determine the scopes Github provides access to. Let’s say, in the application we need access to the user information and repositories: &lt;em&gt;user&lt;/em&gt;, &lt;em&gt;repo&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;After configuring the OAuth Github application, we should have the following data we will add to the project as configuration properties:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;struct AuthConfiguration {
    static let baseUrl = "https://github.com/"
    static let authUri = "login/oauth/authorize"
    static let tokenUri = "login/oauth/access_token"
    static let endSessionUri = "logout"
    static let scopes = ["user", "repo"]
    static let clientId = "..."
    static let clientSecret = "..."
    static let callbackUrl = "ru.kts.oauth://github.com/callback"
    static let logoutCallbackUrl = "ru.kts.oauth://github.com/logout_callback"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Please note that &lt;em&gt;callbackUrl&lt;/em&gt; and &lt;em&gt;logoutCallbackUrl&lt;/em&gt; have the &lt;em&gt;ru.kts.oauth&lt;/em&gt; custom scheme. For the application to be able to intercept the redirect, it’s necessary to write this scheme in &lt;em&gt;Info.plist (URL Schemes, URLIdentifier)&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--OgFePDff--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zapygmltkhgbjc2jgafy.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--OgFePDff--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zapygmltkhgbjc2jgafy.jpg" alt="Image description" width="880" height="256"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Connect AppAuth library via the cocoapods. When working with Github OAuth, there is one nuance — Github returns &lt;a href="https://github.com/openid/AppAuth-iOS/pull/206"&gt;response in the xml format&lt;/a&gt;, while OAuth specification requires response in the json format. You can’t add the custom request header to specify the response format in the AppAuth library, so let’s fork it and &lt;a href="https://github.com/openid/AppAuth-iOS/compare/master...elenakacharmina:AppAuth-iOS:master"&gt;add the headers&lt;/a&gt; to the source code.&lt;/p&gt;

&lt;p&gt;Connecting AppAuth library&lt;/p&gt;

&lt;p&gt;Now, let’s execute authorization.&lt;/p&gt;

&lt;p&gt;📱 &lt;strong&gt;Authorization&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Our task is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;to create the method, in which we will generate the authorization request and call the AppAuth library method&lt;/li&gt;
&lt;li&gt;to implement the redirect intercepting to complete the process&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;1. Create the authorization session.&lt;/strong&gt; For a start, in AppDelegate announce the variable of the OIDExternalUserAgentSession type that will hold the current authorization session and will be used when intercepting the redirect to continue the authorization process (implementation is below):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class AppDelegate: UIResponder, UIApplicationDelegate {

    var currentAuthorizationFlow: OIDExternalUserAgentSession?

}
All classes / protocols of the AppAuth library have OID prefix.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Create the authorization configuration.&lt;/strong&gt; The AppAuth library contains the &lt;strong&gt;OIDAuthorizationService&lt;/strong&gt; entity that allows to execute OAuth requests.&lt;/p&gt;

&lt;p&gt;Requests are presented by &lt;em&gt;OIDAuthorizationRequest&lt;/em&gt; (authorization request), &lt;em&gt;OIDTokenRequest&lt;/em&gt; (token update request), &lt;em&gt;OIDEndSessionRequest&lt;/em&gt; (logout request) entities. Every request requires the &lt;em&gt;OIDServiceConfiguration&lt;/em&gt; configuration — a set of endpoint uris in the initializer. We’ll create the common constant for all requests and put it in a new class of OAuthRepository:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class OAuthRepository {
    private let configuration = OIDServiceConfiguration.init(
        authorizationEndpoint: URL(string: AuthConfiguration.baseUrl + AuthConfiguration.authUri)!,
        tokenEndpoint: URL(string: AuthConfiguration.baseUrl + AuthConfiguration.tokenUri)!,
        issuer: nil,
        registrationEndpoint: nil,
        endSessionEndpoint: URL(string: AuthConfiguration.baseUrl + AuthConfiguration.endSessionUri)!)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In our case, &lt;em&gt;issuer&lt;/em&gt; (The OpenID Connect issuer) and &lt;em&gt;registrationEndpoint&lt;/em&gt; (registration uri) fields are not used.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Create the authorization request.&lt;/strong&gt; The entire OAuth data logic will be located in OAuthRepository class; let’s place the login method there.&lt;/p&gt;

&lt;p&gt;State the request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; let request = OIDAuthorizationRequest(
            configuration: configuration,
            clientId: AuthConfiguration.clientId,
            clientSecret: AuthConfiguration.clientSecret,
            scopes: AuthConfiguration.scopes,
            redirectURL: URL(string: AuthConfiguration.callbackUrl)!,
            responseType: OIDResponseTypeCode,
            additionalParameters: nil)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The &lt;em&gt;responseType&lt;/em&gt; field informs, in what form we want to get the response after authorization. Other possible values are &lt;em&gt;OIDResponseTypeToken&lt;/em&gt;, &lt;em&gt;OIDResponseTypeIDToken&lt;/em&gt;. Github always returns only the code, that’s why let’s use &lt;em&gt;OIDResponseTypeCode&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;additionalParameters&lt;/em&gt; is a dictionary, whose keys will be added to the request URL as additional &lt;em&gt;query&lt;/em&gt; parameters. They can be required for some OAuth services, but we don’t use them in our example&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;4. Execute the authorization request.&lt;/strong&gt; The next step is to call the AppAuth library method to execute the request.&lt;/p&gt;

&lt;p&gt;If an authorized user doesn’t have to open pages in the application in SafariVC, you can get away with the standard &lt;a href="https://github.com/openid/AppAuth-iOS#authorizing-ios"&gt;method&lt;/a&gt; from the AppAuth library documentation. Under the hood, the method uses &lt;em&gt;OIDExternalUserAgentIOS&lt;/em&gt; with a separate &lt;em&gt;ASWebAuthenticationSession&lt;/em&gt; session, which then doesn’t share cookies with other &lt;em&gt;SafariViewControllers&lt;/em&gt;. That’s not what we want.&lt;/p&gt;

&lt;p&gt;To share the session with SavariVC we implement custom &lt;em&gt;OIDExternalUserAgentIOSSafariViewController&lt;/em&gt; that implements the &lt;em&gt;OIDExternalUserAgent&lt;/em&gt; interface from the library. This allows to open pages in &lt;em&gt;SafariViewController&lt;/em&gt; without authorization.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/elenakacharmina/ios-oauth-example/blob/master/ios-oauth-example/Common/OIDExternalUserAgentIOSSafariViewController.swift"&gt;Click here&lt;/a&gt; to see the sample implementation and add it to the project, and read more about UserAgent in &lt;a href="https://backstage.forgerock.com/community/developer"&gt;this article&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Create OIDExternalUserAgentIOSSafariViewController&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let agent = OIDExternalUserAgentIOSSafariViewController(presentingViewController: viewController)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let’s use the authorization method with the custom &lt;em&gt;externalUserAgent&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;appDelegate.currentAuthorizationFlow = OIDAuthState.authState(byPresenting: request, externalUserAgent: agent) { [weak self] authState, error in
    // implementation below
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Calling this method will open SafariVC with the authorization page, and after successful authorization, the service will redirect you to the url stated in the redirectURL field of the request. In our case, it’s &lt;em&gt;ru.kts.oauth://github.com/callback&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The uri redirect parameters contain the &lt;em&gt;code&lt;/em&gt; we need. It’s necessary to intercept this redirect, and then to exchange code for the token.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Now, let’s intercept the authorization redirect and get the token&lt;/strong&gt;. To intercept the redirect, let’s implement &lt;em&gt;scene(&lt;/em&gt;, openURLContexts:)_ method in &lt;em&gt;sceneDelegate&lt;/em&gt;, for ios versions lower than 13 — &lt;em&gt;application&lt;/em&gt;(_:, open url: URL, options:) in AppDelegate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func scene(_ scene: UIScene, openURLContexts URLContexts: Set&amp;lt;UIOpenURLContext&amp;gt;) {
    let appDelegate = UIApplication.shared.delegate as! AppDelegate
    if let authorizationFlow = appDelegate.currentAuthorizationFlow,
       let url = URLContexts.first?.url,
       authorizationFlow.resumeExternalUserAgentFlow(with: url) {
            appDelegate.currentAuthorizationFlow = nil
     }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we check the current authorization flow in the application and call the &lt;em&gt;resumeExternalUserAgentFlow&lt;/em&gt; method that verifies, if this uri coincides with the one stated in the request. If the method returns true, SafariVC hides and the process of exchange the code for the token starts. The code is exchanged for the token under AppAuth hood.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. Save the tokens&lt;/strong&gt;. After exchanging the code for the token, we get in the &lt;em&gt;OIDAuthState.authState&lt;/em&gt; method handler we used during the previous step. It’s necessary to check that there are no errors and to save the obtained token. For simplicity, let’s save access and refresh tokens in UserDefaults:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;appDelegate.currentAuthorizationFlow = OIDAuthState.authState(byPresenting: request, externalUserAgent: agent) { [weak self] authState, error in
    if error == nil {
        let tokenModel = TokenModel(access: authState?.lastTokenResponse?.accessToken,
                                            refresh: authState?.lastTokenResponse?.refreshToken)
        self?.userDefaultsHelper.setToken(value: tokenModel)
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In a real-life project, that would not be safe, so it’s better to use keychain.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. Test the authorization&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--YiddZi3C--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oujldczyo8mxkciaijmk.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YiddZi3C--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oujldczyo8mxkciaijmk.jpg" alt="Image description" width="880" height="621"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Run the code.&lt;/p&gt;

&lt;p&gt;We have already implemented OAuth authorization and token receipt:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XrZt4phy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/65l18k71v2jrn6828yi7.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XrZt4phy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/65l18k71v2jrn6828yi7.jpg" alt="Image description" width="880" height="621"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To validate the obtained token, we push a new screen, where we try to &lt;a href="https://github.com/elenakacharmina/ios-oauth-example/blob/0da54ebf3fa6b0bb93903e73b6c16964954d96c5/ios-oauth-example/Repositories/DataRepository.swift#L17"&gt;get data&lt;/a&gt; from &lt;a href="https://api.github.com/user"&gt;https://api.github.com/user&lt;/a&gt; with the token.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XtdmhKk6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mkl0knr1ssmr00n9k0jz.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XtdmhKk6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mkl0knr1ssmr00n9k0jz.jpg" alt="Image description" width="880" height="621"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To make sure that the authorization session shares cookies with SafariVC, we open the Github user page in it by clicking:&lt;/p&gt;

&lt;p&gt;📱&lt;strong&gt;Token Update&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Refresh token is not used in Github OAuth, but in many other services the access expires. That’s why I provide the token update example.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Generate the token update request.&lt;/strong&gt; For a start, it’s necessary to generate OIDTokenRequest:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func refreshToken() {
    guard let refreshToken = userDefaultsHelper.getToken()?.refresh else { return }
          let requestRefresh = OIDTokenRequest(
              configuration: configuration,
              grantType: OIDGrantTypeRefreshToken,
              authorizationCode: nil,
              redirectURL: nil,
              clientID: AuthConfiguration.clientId,
              clientSecret: AuthConfiguration.clientSecret,
              scope: nil,
              refreshToken: refreshToken,
              codeVerifier: nil,
              additionalParameters: nil)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In addition to the already known parameters we’ve seen during authorization, in the request it’s necessary to transfer the &lt;em&gt;OIDGrantTypeRefreshToken&lt;/em&gt; value and the saved refresh token in the grantType parameter. We transfer nil in the parameters we don’t use.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Update the token.&lt;/strong&gt; Call the &lt;em&gt;OIDAuthorizationService&lt;/em&gt;.perform method, if updated successfully, we save new values of access and refresh tokens:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func refreshToken() {
    guard let refreshToken = userDefaultsHelper.getToken()?.refresh else { return }
    let requestRefresh = OIDTokenRequest(
        configuration: configuration,
        grantType: OIDGrantTypeRefreshToken,
        authorizationCode: nil,
        redirectURL: nil,
        clientID: AuthConfiguration.clientId,
        clientSecret: AuthConfiguration.clientSecret,
        scope: nil,
        refreshToken: refreshToken,
        codeVerifier: nil,
        additionalParameters: nil)

    OIDAuthorizationService.perform(requestRefresh) { [weak self] tokenResponse, error in
        if error == nil {
            let tokenModel = TokenModel(access: tokenResponse?.accessToken,
                                        refresh: tokenResponse?.refreshToken)
            self?.userDefaultsHelper.setToken(value: tokenModel)
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can check this implementation, if you create the &lt;a href="https://docs.github.com/en/apps/creating-github-apps/creating-github-apps/creating-a-github-app"&gt;Github Apps&lt;/a&gt; application instead of OAuth and replace clientId and clientSecret in AuthConfiguration.&lt;/p&gt;

&lt;p&gt;📱&lt;strong&gt;Logout&lt;/strong&gt;&lt;br&gt;
When using OAuth authorization, it’s not enough to just clear the saved token from the storage. SafariVC cookies are not cleared, and when trying to get authorized again, a user will automatically log into the services under the previous account without the opportunity to enter another login / password.&lt;/p&gt;

&lt;p&gt;That’s why, to implement logout, it’s also necessary to generate the request in &lt;em&gt;OIDEndSessionRequest&lt;/em&gt;…&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let request =  OIDEndSessionRequest(configuration: configuration,
                                    idTokenHint: accessToken,
                                    postLogoutRedirectURL: URL(string: AuthConfiguration.logoutCallbackUrl)!,
                                    additionalParameters: nil)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;…and include it in the OIDAuthorizationService.present method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let agent = OIDExternalUserAgentIOSSafariViewController(presentingViewController: viewController)

appDelegate.currentAuthorizationFlow = OIDAuthorizationService.present(request, externalUserAgent: agent) { [weak self] (response, error) in
  if let error = error {
       completion(.failure(CustomError.logoutError))
  } else {
       self?.userDefaultsHelper.clearToken()
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--kpUINJ9T--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/72i9xhb99ujk1zkm2th9.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--kpUINJ9T--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/72i9xhb99ujk1zkm2th9.jpg" alt="Image description" width="880" height="621"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;GitHub OAuth has no redirect after logout, that’s why users will have to close the window by themselves.&lt;/p&gt;

&lt;p&gt;This causes an error in the callback, that’s why there is no error check in the final version, and the app just clears off the saves tokens:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let agent = OIDExternalUserAgentIOSSafariViewController(presentingViewController: viewController)

appDelegate.currentAuthorizationFlow = OIDAuthorizationService.present(request, externalUserAgent: agent) { [weak self] (response, error) in
    self?.userDefaultsHelper.clearToken()
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;📲** Conclusions**&lt;br&gt;
The entier project code can be found in &lt;a href="https://github.com/elenakacharmina/ios-oauth-example"&gt;github&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We’ve implemented all methods required to support OAuth: authorization, token update and logout. Using the library helps easily integrate OAuth in the application. But some services — such as Github OAuth — have their own usage patterns that require custom solutions.&lt;/p&gt;

&lt;p&gt;How do you implement OAuth in your applications? Have you had experience of working with AppAuth? What OAuth issues have you faced?&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Cooking Window Inset with Jetpack Compose sauce and a pinch of View — part 2</title>
      <dc:creator>KTS Studio</dc:creator>
      <pubDate>Tue, 07 Mar 2023 23:39:50 +0000</pubDate>
      <link>https://dev.to/ktssolutions/cooking-window-inset-with-jetpack-compose-sauce-and-a-pinch-of-view-part-2-1pnn</link>
      <guid>https://dev.to/ktssolutions/cooking-window-inset-with-jetpack-compose-sauce-and-a-pinch-of-view-part-2-1pnn</guid>
      <description>&lt;p&gt;Hey! My name is Timur, I am an Android developer at &lt;a href="https://www.kts.solutions/"&gt;KTS&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This article is a continuation. In the previous part, we figured out what &lt;em&gt;edge-to-edge&lt;/em&gt; mode is in mobile apps and how to work with &lt;em&gt;WindowInsets&lt;/em&gt; in Android.&lt;/p&gt;

&lt;p&gt;Now we’ll cover examples of how to handle insets not only in View, but also in &lt;em&gt;Jetpack Compose&lt;/em&gt;. And when you can find the articles about working with Insets in View on the web, the information about working with them in Jetpack Compose can only be found in the official documentation.&lt;/p&gt;

&lt;p&gt;All the examples from the article can be viewed in this &lt;a href="https://github.com/TimurChikishev/Insets/"&gt;repository&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Content of this article:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Examples of processing insets:&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;System Window Insets&lt;/li&gt;
&lt;li&gt;Ime Insets (Keyboard Processing)&lt;/li&gt;
&lt;li&gt;Stable Insets&lt;/li&gt;
&lt;li&gt;Immersive mode (full screen mode without UI elements)&lt;/li&gt;
&lt;li&gt;To hide the UI&lt;/li&gt;
&lt;li&gt;To show the UI&lt;/li&gt;
&lt;li&gt;Display Cutouts (Display cutout support)&lt;/li&gt;
&lt;li&gt;System Gesture Insets&lt;/li&gt;
&lt;li&gt;Mandatory System Gesture Insets&lt;/li&gt;
&lt;li&gt;Tappable element insets&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;System Window Insets&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This type of insets is the main one. They’re needed to handle elements like the &lt;em&gt;Status Bar&lt;/em&gt; and &lt;em&gt;Navigation Bar&lt;/em&gt;. For example, with full screen rendering, the toolbar will be under the Status Bar.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Al7442oD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cpbv9ufv5mkuoke04blv.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Al7442oD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cpbv9ufv5mkuoke04blv.jpeg" alt="Image description" width="880" height="425"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this case, we need to set the &lt;em&gt;insets&lt;/em&gt; for the toolbar that’s in the &lt;a href="https://developer.android.com/reference/com/google/android/material/appbar/AppBarLayout"&gt;AppBarLayout&lt;/a&gt; with a magenta background. This will give us the effect of extending the AppBar behind the Status Bar. The example below uses the &lt;a href="https://github.com/chrisbanes/insetter"&gt;insetter&lt;/a&gt; library.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
toolbar.applyInsetter {
   type(navigationBars = true, statusBars = true) {
       padding(horizontal = true)
       margin(top = true)
   }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In &lt;em&gt;Jetpack Compose&lt;/em&gt;, you can achieve the same effect by setting insets in &lt;em&gt;contentPadding&lt;/em&gt; of the &lt;em&gt;TopAppBar&lt;/em&gt; function. In this example, we use &lt;em&gt;WindowInsets.systemBars&lt;/em&gt; for horizontal and top so that when the screen is rotated, the Navigation Bar doesn’t overlap the beginning of the header or the exit button.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
TopAppBar(
    contentPadding = WindowInsets.systemBars
        .only(WindowInsetsSides.Horizontal + WindowInsetsSides.Top)
        .asPaddingValues(),
    backgroundColor = MaterialTheme.colors.primary
) {
    // content...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Jetpack Compose also has many extensions for &lt;a href="https://developer.android.com/jetpack/compose/modifiers"&gt;Modifier&lt;/a&gt;, such as &lt;a href="https://developer.android.com/reference/kotlin/androidx/compose/foundation/layout/package-summary#(androidx.compose.ui.Modifier).systemBarsPadding()"&gt;systemBarsPadding()&lt;/a&gt;, &lt;a href="https://developer.android.com/reference/kotlin/androidx/compose/foundation/layout/package-summary#(androidx.compose.ui.Modifier).navigationBarsPadding()"&gt;navigationBarsPadding()&lt;/a&gt;, &lt;a href="https://developer.android.com/reference/kotlin/androidx/compose/foundation/layout/package-summary#(androidx.compose.ui.Modifier).statusBarsPadding()"&gt;statusBarsPadding()&lt;/a&gt; and others.&lt;/p&gt;

&lt;p&gt;After installing &lt;em&gt;insets&lt;/em&gt;, the toolbar will look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--OuLdLOyQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lhmp464hp7sda3iy2rkv.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--OuLdLOyQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lhmp464hp7sda3iy2rkv.jpeg" alt="Image description" width="652" height="298"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ime Insets (Keyboard Processing)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The keyboard is handled with &lt;em&gt;WindowInsetsCompat.Type.ime()&lt;/em&gt;. Also for &lt;em&gt;ime insets&lt;/em&gt;, it’s possible to additionally handle keyboard animation with the new API &lt;em&gt;ViewCompat.setWindowInsetsAnimationCallback&lt;/em&gt;. You can learn more about the animation features of ime insets here.&lt;/p&gt;

&lt;p&gt;The &lt;em&gt;setWindowInsetsAnimationCallback&lt;/em&gt; call is implemented in the &lt;a href="https://github.com/chrisbanes/insetter"&gt;insetter&lt;/a&gt; library (activated by the &lt;em&gt;animated&lt;/em&gt; flag on padding/margin) and allows you to additionally link the animation not only to the View on which the DSL is called, but also to other View in order to synchronize the animation on several UI elements (&lt;em&gt;syncTranslationTo&lt;/em&gt; method).&lt;/p&gt;

&lt;p&gt;An example of keypad processing with &lt;a href="https://github.com/chrisbanes/insetter"&gt;insetter&lt;/a&gt; animation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ueAgJN-9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d77tpzf8431ce6o51iod.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ueAgJN-9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d77tpzf8431ce6o51iod.gif" alt="Image description" width="880" height="508"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;private fun setupInsets() = with(binding) {
   messageWrapper.applySystemBarsImeInsetter(syncTranslationView = list) {
       margin(horizontal = true, bottom = true, animated = true)
   }
}

inline fun View.applySystemBarsImeInsetter(
   syncTranslationView: View? = null,
   crossinline insetterApply: InsetterApplyTypeDsl.() -&amp;gt; Unit
) {
   applyInsetter {
       type(ime = true, navigationBars = true, statusBars = true) {
           insetterApply()
       }
       syncTranslationView?.let {
           syncTranslationTo(it)
       }
   }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To achieve this effect in &lt;em&gt;Jetpack Compose&lt;/em&gt;, you need to use the &lt;a href="https://developer.android.com/reference/kotlin/androidx/compose/foundation/layout/package-summary#(androidx.compose.ui.Modifier).imePadding()"&gt;imePadding()&lt;/a&gt; extension for the &lt;a href="https://developer.android.com/jetpack/compose/modifiers"&gt;Modifier&lt;/a&gt; (don’t forget to inset the Navigation Bar with &lt;a href="https://developer.android.com/reference/kotlin/androidx/compose/foundation/layout/package-summary#(androidx.compose.ui.Modifier).navigationBarsPadding()"&gt;navigationBarsPadding())&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Composable
fun BottomEditText(
    placeholderText: String = "Type text here..."
) {
    val text = rememberSaveable(stateSaver = TextFieldValue.Saver) {
        mutableStateOf(TextFieldValue(""))
    }
    Surface(elevation = 1.dp) {
        OutlinedTextField(
            value = text.value,
            onValueChange = { text.value = it },
            placeholder = { Text(text = placeholderText) },
            modifier = Modifier
                .fillMaxWidth()
                .padding(horizontal = 16.dp, vertical = 8.dp)
                .navigationBarsPadding()
                .imePadding()
        )
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Jetpack Compose&lt;/em&gt; also allows you to open the keyboard with a scroll using &lt;a href="https://developer.android.com/reference/kotlin/androidx/compose/foundation/layout/package-summary#(androidx.compose.ui.Modifier).imeNestedScroll()"&gt;imeNestedScroll()&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LazyColumn(
    contentPadding = contentPadding,
    reverseLayout = true,
    modifier = Modifier
        .weight(1f)
        .imeNestedScroll()
) {
    items(listItems) { SampleListItem(it) }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--htmw0X05--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7704njezoxks4gmeom87.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--htmw0X05--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7704njezoxks4gmeom87.gif" alt="Image description" width="880" height="508"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stable Insets&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Stable Insets are only useful in full-screen applications such as video players, galleries, and games. In player mode, you may have noticed that you have the entire UI system hidden, including the Status Bar, which moves across the edge of the screen. But as soon as you touch the screen, the Status Bar appears at the top. The peculiarity of the insets in the gallery or player is that empty &lt;em&gt;insets&lt;/em&gt; come into &lt;em&gt;View&lt;/em&gt; when you show/hide the UI system (when the UI system is hidden). For this reason, the Ui elements of the application that work with &lt;em&gt;insets&lt;/em&gt; can jump back and forth, as you can see in the following image.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ICqT1CLy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/efq9gg9spkyirn4k9j6b.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ICqT1CLy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/efq9gg9spkyirn4k9j6b.gif" alt="Image description" width="840" height="484"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Therefore, there’s a special type of &lt;em&gt;Stable Insets&lt;/em&gt; that the system always outputs with values as if the system UI were displayed. The &lt;a href="https://github.com/chrisbanes/insetter"&gt;insetter&lt;/a&gt; has a method &lt;em&gt;ignoreVisibility&lt;/em&gt; which tells the system that there should be &lt;em&gt;Stable Insets&lt;/em&gt; for this &lt;em&gt;View&lt;/em&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;toolbar.applyInsetter {
    type(navigationBars = true, statusBars = true) {
        padding(horizontal = true)
            margin(top = true)
    }
    ignoreVisibility(true)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Surprisingly, there’s no ready-made solution for &lt;em&gt;Stable Insets&lt;/em&gt; in &lt;em&gt;Jetpack Compose&lt;/em&gt;, but we can implement it as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class StableStatusBarsInsetsHolder {
   private var stableStatusBarsInsets: WindowInsets = WindowInsets(0.dp)

   val stableStatusBars: WindowInsets
       @Composable
       get() {
           val density = LocalDensity.current
           val layoutDirection = LocalLayoutDirection.current
           val statusBars = WindowInsets.statusBars
           return remember {
               derivedStateOf {
                   if (statusBars.exclude(stableStatusBarsInsets).getTop(density) &amp;gt; 0) {
                       stableStatusBarsInsets 
                                   = statusBars.deepCopy(density, layoutDirection)
                   }
                   stableStatusBarsInsets
               }
           }.value
       }

}

private fun WindowInsets.deepCopy(density: Density, layoutDirection: LayoutDirection): WindowInsets {
   return WindowInsets(
       left = getLeft(density, layoutDirection),
       top = getTop(density),
       right = getRight(density, layoutDirection),
       bottom = getBottom(density)
   )
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example of use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;val stableInsetsHolder = remember { StableStatusBarsInsetsHolder()}
SampleTopBar(
   titleRes = R.string.insets_sample_fullscreen_stable,
   contentPadding = stableInsetsHolder.stableStatusBars
       .only(WindowInsetsSides.Horizontal + WindowInsetsSides.Top)
       .asPaddingValues(),
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, when we use &lt;em&gt;Stable Insets&lt;/em&gt;, the processing &lt;em&gt;insets&lt;/em&gt; elements don’t bounce.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---K3EOsuP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e2mfubx814punhuwibzx.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---K3EOsuP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e2mfubx814punhuwibzx.gif" alt="Image description" width="831" height="479"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Immersive mode (full screen mode without UI elements)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To show or hide the Ui instead of putting certain UI_FLAGS combinations, a new class &lt;em&gt;WindowInsetsController&lt;/em&gt; (its compat version &lt;em&gt;WindowInsetsControllerCompat&lt;/em&gt;) is used since API 30 which has a convenient API based on the new API 30 classes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--kGOdMywm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cx4ej6i6xks4m7jxkt3r.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--kGOdMywm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cx4ej6i6xks4m7jxkt3r.gif" alt="Image description" width="835" height="482"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;How you can return the system Ui to the screen is set with the WindowInsetsController flags (set via the &lt;em&gt;setSystemBarsBehavior&lt;/em&gt; method):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;BEHAVIOR_SHOW_BARS_BY_SWIPE — A swipe is required to return SystemUi, remove on your own;&lt;/li&gt;
&lt;li&gt;BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE — A swipe is required to return the system Ui, hiding automatically after a while.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are the following extension functions with the ability to add calls to the &lt;a href="https://developer.android.com/reference/android/view/WindowInsetsController"&gt;WindowInsetsController&lt;/a&gt; via extraAction.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;To hide the UI&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fun Window.hideSystemUi(extraAction:(WindowInsetsControllerCompat.() -&amp;gt; Unit)? = null) {
    WindowInsetsControllerCompat(this, this.decorView).let { controller -&amp;gt;
        controller.hide(WindowInsetsCompat.Type.systemBars())
        extraAction?.invoke(controller)
    }
}

// Usage
hideSystemUi{
    systemBarsBehavior =
    WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To show the UI&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fun Window.showSystemUi(extraAction: (WindowInsetsControllerCompat.() -&amp;gt; Unit)? = null) {
    WindowInsetsControllerCompat(this, this.decorView).let { controller -&amp;gt;
        controller.show(WindowInsetsCompat.Type.systemBars())
        extraAction?.invoke(controller)
    }
}

// Usage
showSystemUi()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In &lt;em&gt;Jetpack Compose&lt;/em&gt;, you can use the &lt;em&gt;rememberSystemUiController&lt;/em&gt;() from the &lt;a href="https://google.github.io/accompanist/systemuicontroller/"&gt;accompanist system ui controller&lt;/a&gt; library to hide or show the system user interface.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;val systemUiController = rememberSystemUiController()

InsetsExamplesTheme {
   FullscreenCutoutSample(
       systemUiController.toggleUi
   )
}

val SystemUiController.toggleUi: () -&amp;gt; Unit
   get() = {
       isSystemBarsVisible = !isSystemBarsVisible
   }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But this library doesn’t allow changing flags of &lt;a href="https://developer.android.com/reference/androidx/core/view/WindowInsetsControllerCompat"&gt;WindowInsetsControllerCompat&lt;/a&gt;. So a class based on &lt;em&gt;rememberSystemUiController&lt;/em&gt;() from the &lt;a href="https://google.github.io/accompanist/systemuicontroller/"&gt;accompanist systemui controller&lt;/a&gt; was written. In this implementation, BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE is set for &lt;a href="https://developer.android.com/reference/androidx/core/view/WindowInsetsControllerCompat"&gt;WindowInsetsControllerCompat&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://gist.github.com/lockbooks/2f83a5c9a62180fe6934cf52556b884b"&gt;Example&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Example of use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;val systemUiVisibilityController = rememberSystemUiVisibilityController()
InsetsExamplesTheme {
   FullscreenCutoutSample(
       systemUiVisibilityController.toggleUi
   )
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Display Cutouts (Display cutout support)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Bangs and cutouts are appearing more and more frequently on cell phones. They’re located in different places on the screen and can have different sizes and shapes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--B8-shTd2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/43413xjz45b7zqfow4zi.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--B8-shTd2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/43413xjz45b7zqfow4zi.jpg" alt="Image description" width="880" height="252"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Android 9 (api 28) introduced the &lt;em&gt;DisplayCutout&lt;/em&gt; class, which allows you to handle the cutout area. In addition, there’s a set of flags in &lt;em&gt;WindowManager.LayoutParams&lt;/em&gt; that allow you to enable different behavior around the cutouts.&lt;/p&gt;

&lt;p&gt;To set the flags, &lt;a href="https://developer.android.com/reference/android/view/WindowManager.LayoutParams#layoutInDisplayCutoutMode"&gt;layoutInDisplayCutoutMode&lt;/a&gt; is used to determine how your content is displayed in the cutout area. There’re the following values:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://developer.android.com/reference/android/view/WindowManager.LayoutParams#LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT"&gt;LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT&lt;/a&gt; — in portrait mode the content is displayed below the cutout area, and in landscape mode there will be a black bar.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developer.android.com/reference/android/view/WindowManager.LayoutParams#LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES"&gt;LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES&lt;/a&gt; — Content is displayed in the cutout area in both portrait and landscape modes.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developer.android.com/reference/android/view/WindowManager.LayoutParams#LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER"&gt;LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER&lt;/a&gt; — Content is never displayed in the cutout area.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developer.android.com/reference/android/view/WindowManager.LayoutParams#LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS"&gt;LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS&lt;/a&gt; — In this mode, the window expands beneath the cutouts at all edges of the display in both portrait and landscape orientations, regardless of whether the window hides the system panels ( &lt;a href="https://developer.android.com/reference/android/view/WindowManager.LayoutParams#LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES"&gt;LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES&lt;/a&gt; full analogue, but available with 28 api when &lt;a href="https://developer.android.com/reference/android/view/WindowManager.LayoutParams#LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS"&gt;LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS&lt;/a&gt; appeared only in 30 api).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example of use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if (Build.VERSION.SDK_INT &amp;gt;= Build.VERSION_CODES.P) {
    window.attributes.layoutInDisplayCutoutMode =  WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is a flag for &lt;em&gt;displayCutout&lt;/em&gt; in the &lt;a href="https://chrisbanes.github.io/insetter/"&gt;insetter&lt;/a&gt; library, so you can handle padding and margin from the cutout area.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;applyInsetter {
   type(displayCutout = true) {
       // specify the sides you need
       // for example padding(top = true)
       padding() 
   } 
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In &lt;em&gt;Jetpack Compose&lt;/em&gt; you also need to use &lt;em&gt;layoutInDisplayCutoutMode&lt;/em&gt; to set the mode for &lt;em&gt;DisplayCotout&lt;/em&gt;. In compose, you can use &lt;a href="https://developer.android.com/reference/android/view/WindowInsets#getDisplayCutout()"&gt;WindowInsets.displayCutout&lt;/a&gt; or &lt;a href="https://developer.android.com/reference/kotlin/androidx/compose/foundation/layout/package-summary#(androidx.compose.ui.Modifier).displayCutoutPadding()"&gt;Modifier.displayCutoutPadding()&lt;/a&gt; to handle padding.&lt;/p&gt;

&lt;p&gt;If you suddenly need to get a cutout area, you can use &lt;a href="https://developer.android.com/reference/androidx/core/view/DisplayCutoutCompat#getBoundingRects()"&gt;DisplayCutoutCompat.getBoundingRects()&lt;/a&gt; to do that. This method returns a list of rectangles, each of which is a bounding rectangle for a non-functional area on the display.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ViewCompat.setOnApplyWindowInsetsListener(root) { view, insets -&amp;gt;
   val boundingRects = insets.displayCutout?.boundingRects
   insets
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Jetpack Compose, you can’t get boundingRects with &lt;a href="https://developer.android.com/reference/kotlin/androidx/compose/foundation/layout/package-summary#(androidx.compose.foundation.layout.WindowInsets.Companion).displayCutout()"&gt;WindowInsets.displayCutout&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I think that arranging elements relative to the cutout makes no sense for the following reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;where there’s no cutout is information from the Status Bar (time, icons)&lt;/li&gt;
&lt;li&gt;api returns a list, which means there can be several cutouts&lt;/li&gt;
&lt;li&gt;cutouts come in different shapes and sizes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In general, everything says that it’s better not to do so, but if you really need, you can do it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;System Gesture Insets&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This kind of insets appeared in Android 10. They return the gesture areas home from below and back to the right and left of the screen.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--f4yOjbMf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gnf0s001z6z9dmr766te.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--f4yOjbMf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gnf0s001z6z9dmr766te.jpg" alt="Image description" width="880" height="508"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If there’re scroll elements in these areas, users can, for example, accidentally trigger a backward gesture. Therefore, if you can’t move the interface element away from the edge, but you need to fix the problem with gestures, use this type of inserts&lt;/p&gt;

&lt;p&gt;For example: in the gif below, the user wants to scroll but will trigger an exit without excluding gestures. You should also remember that large areas with gesture exclusions can lead to incomprehensible behavior for the user, so it’s worth excluding gestures only in places where it’s necessary. If we look at the example below, a gesture should only be excluded for an element with a horizontal scroll.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Fq6ub8ta--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e6y1md3chnjfodat3k9x.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Fq6ub8ta--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e6y1md3chnjfodat3k9x.gif" alt="Image description" width="880" height="648"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While implementing the example for View, I encountered a problem that windowInsets.getInsets(WindowInsetsCompat.Type.systemGestures()) returns an empty value. I agonized over this for a long time, until I decided to run the example on an emulator and saw that everything worked. To make sure, I sent the apk to my colleagues and got the answer: everything works. The problem was that I was testing the example on my device (Realme c21). Vendors are messing around as usual.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://gist.github.com/lockbooks/cf44c65595cfcc27f8aefd0eac34f00e"&gt;Code&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To exclude gestures in Jetpack Compose, you can use &lt;a href="https://developer.android.com/reference/kotlin/androidx/compose/foundation/package-summary#(androidx.compose.ui.Modifier).systemGestureExclusion()"&gt;Modifier.systemGestureExclusion&lt;/a&gt;().&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LazyRow(
   modifier = Modifier
       .padding(vertical = 16.dp)
       .systemGestureExclusion(),
   contentPadding = PaddingValues(horizontal = 16.dp),
   horizontalArrangement = Arrangement.spacedBy(16.dp)
) {
    //...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Mandatory System Gesture Insets&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This type of insets appeared in Android 10 and is a subtype of System Gesture Insets. They specify areas of the screen where system gesture behavior will always take priority over in-app gestures. Mandatory gesture areas can never be excluded by apps (with android 10, the mandatory gesture area is the home gesture area). Mandatory insets move content away from mandatory gesture inserts. For example, if you have a seekbar at the bottom of the screen, you need to use Mandatory System Gesture Insets to avoid calling gestures.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vSEFIlJx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fi5umyqt6lgdp6e1f3r0.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vSEFIlJx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fi5umyqt6lgdp6e1f3r0.gif" alt="Image description" width="880" height="440"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In View, mandatory &lt;em&gt;insets&lt;/em&gt; can be processed using the &lt;a href="https://chrisbanes.github.io/insetter/"&gt;insetter&lt;/a&gt; library:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;seekBar.applyInsetter {
   type(mandatorySystemGestures = true) {
       padding(bottom = true)
   }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Jetpack Compose, mandatory gestures can be obtained with &lt;em&gt;WindowInsets.mandatorySystemGestures&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Surface(
   modifier = Modifier
       .fillMaxWidth()
       .windowInsetsPadding(
           WindowInsets.mandatorySystemGestures
               .only(WindowInsetsSides.Bottom)
               .union(
                   WindowInsets.navigationBars.only(WindowInsetsSides.Horizontal)
               )
       ),
   color = Color.LightGray.copy(alpha = 0.3f)
) {
   // content
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Tappable element insets&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This type of insets appeared in android 10 and is needed to handle different Navigation Bar modes. This type of &lt;em&gt;insets&lt;/em&gt; is rarely used, because the differences from the &lt;em&gt;system window insets&lt;/em&gt; are minimal. But you should agree that it’s nice to know that your application is written in the coolest way possible.&lt;/p&gt;

&lt;p&gt;In the picture below you can see that &lt;em&gt;tappable element&lt;/em&gt; insets and &lt;em&gt;system window insets&lt;/em&gt; work the same when the device is set to navigate with buttons. The difference can only be noticed in gesture-based navigation mode.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--KhQkDbAD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/al0e0fpj0pse2q25u965.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KhQkDbAD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/al0e0fpj0pse2q25u965.jpeg" alt="Image description" width="880" height="718"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The point is that when navigating with gestures, we don’t click, but swipe from bottom to top. This means that we can use interactive elements in this area (e.g. &lt;a href="https://m2.material.io/components/buttons-floating-action-button/android"&gt;FloatingActionButton&lt;/a&gt;), which means that we don’t need to indent and tappable element insets will return 0.&lt;/p&gt;

&lt;p&gt;An example of using the c view (still using the &lt;a href="https://chrisbanes.github.io/insetter/"&gt;insetter&lt;/a&gt; library):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fab.applyInsetter {
   type(tappableElement = true) {
       margin(bottom = true)
   }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In &lt;em&gt;Jetpack Compose&lt;/em&gt; we use &lt;a href="https://developer.android.com/reference/kotlin/androidx/core/view/WindowInsetsCompat.Type#tappableElement()"&gt;WindowInsets.tappableElement&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Scaffold(
   floatingActionButton = {
      FloatingActionButton(
          modifier = Modifier.padding(
              bottom = WindowInsets.tappableElement
                  .only(WindowInsetsSides.Bottom)
                  .asPaddingValues()
                  .calculateBottomPadding()
          ),
          backgroundColor = backgroundColor,
          onClick = onClick
      ) {
          Icon(
              imageVector = Icons.Filled.Add,
              contentDescription = null
          )
      }
){ // content... }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You can find all the code from the article in &lt;a href="https://github.com/TimurChikishev/Insets"&gt;this&lt;/a&gt; repository.&lt;/p&gt;

&lt;p&gt;We looked at the nuances of implementing &lt;em&gt;e2e&lt;/em&gt; in Android mobile apps and an example of implementation using the &lt;a href="https://chrisbanes.github.io/insetter/"&gt;insetter&lt;/a&gt; library for View, and used the built-in insets in &lt;em&gt;Jetpack Compose&lt;/em&gt;. In this article I wanted to let all android developers know that &lt;em&gt;e2e&lt;/em&gt; is in fact very easy to implement and worth the time invested. I hope you found this article useful and more and more developers will adopt &lt;em&gt;e2e&lt;/em&gt; in their applications.&lt;/p&gt;

&lt;p&gt;Do you implement edge-to-edge in your applications? Do you use libraries to work with insets? What problems have you faced when implementing edge-to-edge?&lt;/p&gt;

&lt;p&gt;Thanks to Vadim for his help in preparing this article.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Cooking Window Inset with Jetpack Compose sauce and a pinch of View — part 1</title>
      <dc:creator>KTS Studio</dc:creator>
      <pubDate>Tue, 28 Feb 2023 17:24:59 +0000</pubDate>
      <link>https://dev.to/ktssolutions/cooking-window-inset-with-jetpack-compose-sauce-and-a-pinch-of-view-part-1-527m</link>
      <guid>https://dev.to/ktssolutions/cooking-window-inset-with-jetpack-compose-sauce-and-a-pinch-of-view-part-1-527m</guid>
      <description>&lt;p&gt;Hey! My name is Timur, I am an Android developer at &lt;a href="https://www.kts.solutions/" rel="noopener noreferrer"&gt;KTS&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Unfortunately, there’re still Android applications that don’t support &lt;em&gt;edge-to-edge&lt;/em&gt;. I feel that developers either don’t know about this possibility or are afraid to work with &lt;em&gt;WindowInsets&lt;/em&gt;. In fact, Edge-to-Edge isn’t difficult to implement, and thanks to this article you’ll understand this topic much faster.&lt;/p&gt;

&lt;p&gt;Today I’ll explain what &lt;em&gt;edge-to-edge&lt;/em&gt; mode is in mobile apps and how to work with &lt;em&gt;WindowInsets&lt;/em&gt; in Android. In the next article, we’ll also cover examples of how to handle insets not only in View, but also in &lt;em&gt;Jetpack Compose&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;When you can find the articles about working with &lt;em&gt;Insets&lt;/em&gt; in &lt;em&gt;View&lt;/em&gt; on the web, the information about working with them in &lt;em&gt;Jetpack Compose&lt;/em&gt; can only be found in the official documentation.&lt;/p&gt;

&lt;p&gt;All the examples from the article can be viewed in &lt;a href="https://github.com/TimurChikishev/Insets/" rel="noopener noreferrer"&gt;this&lt;/a&gt; repository.&lt;/p&gt;

&lt;p&gt;The content of the article:&lt;/p&gt;

&lt;p&gt;1️⃣ What is edge-to-edge?&lt;br&gt;
2️⃣ Steps for setting up edge-to-edge&lt;br&gt;
3️⃣ Changing the color of the system UI&lt;br&gt;
4️⃣ Request a rendering of the application under the system UI&lt;br&gt;
5️⃣ Eliminate visual conflicts&lt;br&gt;
6️⃣ WindowInsets vs fitSystemWindow?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is edge-to-edge?&lt;/strong&gt;&lt;br&gt;
Today, mobile applications are increasingly displayed across the entire visible surface of the display, beyond the system UI. These applications use an &lt;em&gt;edge-to-edge&lt;/em&gt; approach, where the application is displayed below the system UI, i.e., the status bar and the navigation bar.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv377g9gpgoqupa074cla.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv377g9gpgoqupa074cla.jpeg" alt=" " width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Why, you ask? To create a more attractive and modern user interface. Will you agree that everyone is happier when they use a nicer application?&lt;/p&gt;

&lt;p&gt;Now let’s move on to the &lt;em&gt;edge-to-edge&lt;/em&gt; implementation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Steps for setting up edge-to-edge&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To implement &lt;em&gt;edge-to-edge&lt;/em&gt; mode in your application you’ll need to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;change the color of the system UI&lt;/li&gt;
&lt;li&gt;request a rendering of the application under the system UI&lt;/li&gt;
&lt;li&gt;eliminate visual conflicts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Changing the color of the system UI&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;As of Android 5 (API 21), it’s now possible to set the color for the Status Bar and Navigation Bar. Use the following theme attributes for this purpose:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;item name="android:statusBarColor"&amp;gt;@color/colorAccent&amp;lt;/item&amp;gt;
&amp;lt;item name="android:navigationBarColor"&amp;gt;@color/colorAccent&amp;lt;/item&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can also make the color of the system UI transparent or semi-transparent. To achieve semi-transparency, just set &lt;a href="https://developer.android.com/reference/android/R.attr#windowTranslucentStatus" rel="noopener noreferrer"&gt;android:windowTranslucentStatus&lt;/a&gt; and &lt;a href="https://developer.android.com/reference/android/R.attr#windowTranslucentNavigation" rel="noopener noreferrer"&gt;android:windowTranslucentNavigation&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;item name="android:windowTranslucentStatus"&amp;gt;true&amp;lt;/item&amp;gt;
&amp;lt;item name="android:windowTranslucentNavigation"&amp;gt;true&amp;lt;/item&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa3xjkdbjgklvvbf8vzjy.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa3xjkdbjgklvvbf8vzjy.jpeg" alt=" " width="800" height="297"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To get a fully transparent system interface you need to set &lt;a href="https://developer.android.com/reference/android/R.attr#navigationBarColor" rel="noopener noreferrer"&gt;android:navigationBarColor&lt;/a&gt; and &lt;a href="https://developer.android.com/reference/android/R.attr#statusBarColor" rel="noopener noreferrer"&gt;android:statusBarColor&lt;/a&gt; with transparent color and disable contrast with the following attributes &lt;a href="https://developer.android.com/reference/android/view/Window#isNavigationBarContrastEnforced()" rel="noopener noreferrer"&gt;android:enforceNavigationBarContrast&lt;/a&gt;, &lt;a href="https://developer.android.com/reference/android/view/Window#isStatusBarContrastEnforced()" rel="noopener noreferrer"&gt;android:enforceStatusBarContrast&lt;/a&gt;. Disabling contrast is necessary because since version 10 Android provides sufficient contrast to the Navigation Bar.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;item name="android:statusBarColor"&amp;gt;@android:color/transparent&amp;lt;/item&amp;gt;
&amp;lt;item name="android:navigationBarColor"&amp;gt;@android:color/transparent&amp;lt;/item&amp;gt;
&amp;lt;item name="android:enforceNavigationBarContrast"&amp;gt;false&amp;lt;/item&amp;gt;
&amp;lt;item name="android:enforceStatusBarContrast"&amp;gt;false&amp;lt;/item&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftwqthdmwlkv3fh1hqyzk.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftwqthdmwlkv3fh1hqyzk.jpg" alt=" " width="800" height="297"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the screenshot above, you may see that the Navigation Bar buttons are barely visible. The Status Bar would have the same problem if it weren’t for the magenta color. To fix this, use the attributes &lt;a href="https://developer.android.com/reference/android/R.attr#windowLightStatusBar" rel="noopener noreferrer"&gt;android:windowLightStatusBar&lt;/a&gt;, &lt;a href="https://developer.android.com/reference/android/R.attr#windowLightNavigationBar" rel="noopener noreferrer"&gt;android:windowLightNavigationBar&lt;/a&gt;. Note that &lt;a href="https://developer.android.com/reference/android/R.attr#windowLightStatusBar" rel="noopener noreferrer"&gt;windowLightStatusBar&lt;/a&gt; is available with 23 api and &lt;a href="https://developer.android.com/reference/android/R.attr#windowLightNavigationBar" rel="noopener noreferrer"&gt;windowLightNavigationBar&lt;/a&gt; with 27 api.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsly41xphiw0hmk7arqn3.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsly41xphiw0hmk7arqn3.jpg" alt=" " width="800" height="296"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In &lt;em&gt;Jetpack Compose&lt;/em&gt;, you can use the &lt;a href="https://github.com/google/accompanist/tree/main/systemuicontroller" rel="noopener noreferrer"&gt;System UI Controller&lt;/a&gt; library to change the color, which provides simple tools for changing the color of the UI system. You can change the color of the UI system using this library as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;private val BlackScrim = Color(0f, 0f, 0f, 0.3f) // 30% opaque black

@Composable
fun TransparentSystemBars() {
   val systemUiController = rememberSystemUiController()
   val useDarkIcons = MaterialTheme.colors.isLight
   SideEffect {
       systemUiController.setSystemBarsColor(
           color = Color.Transparent,
           darkIcons = useDarkIcons,
           isNavigationBarContrastEnforced = false,
           transformColorForLightContent = { original -&amp;gt;
               BlackScrim.compositeOver(original)
           }
       )
   }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example of using the TransparentSystemBars function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;override fun onCreate(savedInstanceState: Bundle?) { 
   super.onCreate(savedInstanceState)
   WindowCompat.setDecorFitsSystemWindows(window, false)
   setContent {
       TransparentSystemBars()
       Sample()
   }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;em&gt;setSystemBarsColor&lt;/em&gt; method allows you to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;set the color for the system UI&lt;/li&gt;
&lt;li&gt;specify when to use light or dark icons&lt;/li&gt;
&lt;li&gt;you can disable contrast with &lt;em&gt;isNavigationBarContrastEnforced&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;you can use the &lt;em&gt;transformColorForLightContent&lt;/em&gt; lambda, which will be called to convert colors if dark icons are requested but not available. The default behavior in &lt;em&gt;transformColorForLightContent&lt;/em&gt; is black, so you don’t need to write that).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Request a rendering of the application under the system UI&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In addition to changing the color of the system interface, you need to tell the system to draw the application in full-screen mode. Android has special &lt;em&gt;View&lt;/em&gt; SYSTEM_UI_FLAGS for this purpose (hereafter UI _FLAGS). They’ve been deprecated since API 30, and you should now use the new WindowCompat class, which contains the required flags on earlier versions of the API.&lt;/p&gt;

&lt;p&gt;The full screen mode request via &lt;a href="https://developer.android.com/reference/androidx/core/view/WindowCompat" rel="noopener noreferrer"&gt;WindowCompat&lt;/a&gt; looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WindowCompat.setDecorFitsSystemWindows(window, false)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you apply this in the activity, the framework won’t replace inserts for the content of your application and we’ll have to do it manually.&lt;/p&gt;

&lt;p&gt;It makes sense to request rendering mode for the entire application (in onCreate() of your Activity, if you use the single-activity approach).&lt;/p&gt;

&lt;p&gt;In &lt;em&gt;Jetpack Compose&lt;/em&gt;, this step is no different.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Eliminate visual conflicts&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you follow the previous steps and run the application, you can see that the system no longer accounts for the space for the system UI. Now we have to do it ourselves:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4qqjx8or5lvzqjbrb29l.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4qqjx8or5lvzqjbrb29l.jpg" alt=" " width="793" height="234"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Android applications use &lt;a href="https://developer.android.com/reference/android/view/WindowInsets" rel="noopener noreferrer"&gt;WindowInsets&lt;/a&gt; to manage the system UI. &lt;em&gt;Insets&lt;/em&gt; is an object that represents an area of a window that conflicts with the application. Conflicts can be different, and there’re different types of insets for that (gesture handling areas, system panels, bangs, etc.).&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://developer.android.com/reference/androidx/core/view/WindowInsetsCompat" rel="noopener noreferrer"&gt;WindowInsetsCompat&lt;/a&gt; class with backward compatibility and convenient separation into insets types is used for insets processing.&lt;/p&gt;

&lt;p&gt;The processing itself consists of attaching a listener to the &lt;em&gt;View&lt;/em&gt;, to which the system passes an &lt;em&gt;insets&lt;/em&gt; object. After you get the object, you can apply the desired padding or margin to the &lt;em&gt;View&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
ViewCompat.setOnApplyWindowInsetsListener(navBar) { view, insets -&amp;gt;
    val systemBarInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars())
    view.updatePadding(
        bottom = systemBarInsets.bottom,
        left = systemBarInsets.left,
        right = systemBarInsets.right
    )
    insets
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In KTS, we use the &lt;a href="https://github.com/chrisbanes/insetter" rel="noopener noreferrer"&gt;insetter&lt;/a&gt; library, which works on the basis of this approach. The library implements a handy Kotlin DSL applyInsetter to handle &lt;em&gt;insets&lt;/em&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;toolbar.applyInsetter {
    type(navigationBars = true, statusBars = true) {
        padding(horizontal = true, top = true)
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Jetpack Compose&lt;/em&gt; used to use the library from the &lt;a href="https://github.com/google/accompanist/tree/main/insets" rel="noopener noreferrer"&gt;accompanist&lt;/a&gt; repository to install instes, but it’s obsolete now that official support for &lt;em&gt;insets&lt;/em&gt; is available in &lt;em&gt;Compose 1.2.0.&lt;/em&gt; If you were already using &lt;a href="https://github.com/google/accompanist/tree/main/insets" rel="noopener noreferrer"&gt;accompanist&lt;/a&gt;, there’s a detailed migration guide on the website.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TopAppBar( 
  contentPadding = WindowInsets.systemBars
        .only(WindowInsetsSides.Horizontal + WindowInsetsSides.Top) 
        .asPaddingValues(), 
  backgroundColor = MaterialTheme.colors.primary 
) { 
  // content… 
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;WindowInsets vs fitSystemWindow?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Android has the &lt;em&gt;fitSystemWindow&lt;/em&gt; flag. If you set it to “true”, this flag adds padding to the container for which you specified the flag.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;FitsSystemWindows&lt;/em&gt; confuses many developers.&lt;/p&gt;

&lt;p&gt;For example, this flag works with CoordinatorLayout and doesn’t work for FrameLayout. FitsSystemWindows = true doesn’t move your content under the status bar, but it works for layouts like CoordinatorLayout and DrawerLayout because they override the default behavior. Under the hood, they set the flags setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) if fitsSystemWindows is &lt;em&gt;true&lt;/em&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://developer.android.com/reference/android/view/View#SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN" rel="noopener noreferrer"&gt;SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN&lt;/a&gt; moves the content under the status bar.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developer.android.com/reference/android/view/View#SYSTEM_UI_FLAG_LAYOUT_STABLE" rel="noopener noreferrer"&gt;SYSTEM_UI_FLAG_LAYOUT_STABLE&lt;/a&gt; ensures that the maximum possible system insets are applied, even if the current ones are less than this.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If we set these flags to our rootLayout, everything should work even on FrameLayout.&lt;/p&gt;

&lt;p&gt;Also, hierarchy is important when using &lt;em&gt;fitSystemWindow&lt;/em&gt;: if any parent has set this flag to “true”, its propagation won’t be taken into account further, because the container has already applied the indents.&lt;/p&gt;

&lt;p&gt;This is actually not all the nuances why using the &lt;em&gt;fitSystemWindow&lt;/em&gt; flag isn’t recommended, so better use WindowInsets to avoid different problems and non-obvious behavior.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What’s next?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That’s all for now, but stay tuned ✌️&lt;br&gt;
In the next part we will look at examples of processing different types of inserts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;System Window Insets&lt;/li&gt;
&lt;li&gt;Ime Insets (Keyboard Processing)&lt;/li&gt;
&lt;li&gt;Stable Insets&lt;/li&gt;
&lt;li&gt;Immersive mode (full screen mode without UI elements)&lt;/li&gt;
&lt;li&gt;To hide the UI&lt;/li&gt;
&lt;li&gt;To show the UI&lt;/li&gt;
&lt;li&gt;Display Cutouts (Display cutout support)&lt;/li&gt;
&lt;li&gt;System Gesture Insets&lt;/li&gt;
&lt;li&gt;Mandatory System Gesture Insets&lt;/li&gt;
&lt;li&gt;Tappable element insets&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;See you later!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>aws</category>
      <category>cli</category>
      <category>devops</category>
      <category>cloudcomputing</category>
    </item>
  </channel>
</rss>
