main

square/leakcanary

Last updated at: 29/12/2023 09:37

NavigatingActivity.kt

TLDR

The NavigatingActivity.kt file is a Kotlin file in the leakcanary.internal.navigation package. It contains an abstract class called NavigatingActivity that extends the Activity class. This class provides functionality for managing and navigating between screens in an Android app.

Methods

installNavigation

This method is responsible for setting up the navigation functionality in the activity. It takes two parameters - savedInstanceState (the saved state of the activity) and container (the container view where screens will be displayed). It initializes the backstack and currentScreen properties based on the saved state or the intent. It creates a view for the current screen and adds it to the container. It also sets up the action bar and updates the screen.

onNewIntent

This method is called when a new intent is received by the activity. It parses the screens from the new intent and updates the backstack and current screen based on the parsed screens.

parseIntentScreens

This abstract method is responsible for parsing the screens from an intent. Subclasses of NavigatingActivity must override this method to provide the implementation for parsing the screens.

getLauncherScreen

This method returns the launcher screen for the activity. Subclasses of NavigatingActivity can override this method to provide their own launcher screen implementation.

onSaveInstanceState

This method is called to save the state of the activity. It saves the current screen and backstack to the provided outState bundle.

onBackPressed

This method is called when the back button is pressed. If there are screens in the backstack, it goes back to the previous screen. If the backstack is empty, it calls the superclass implementation.

resetTo

This method resets the activity to a specified screen. It removes the current view, clears the backstack, creates a new view for the specified screen, and adds the new view to the container.

goTo

This method navigates to a specified screen. It removes the current view, adds the current screen and view to the backstack, creates a new view for the specified screen, and adds the new view to the container.

refreshCurrentScreen

This method refreshes the current screen by removing the current view and creating a new view for the current screen.

goBack

This method goes back to the previous screen in the backstack. It removes the current view, restores the previous screen and view from the backstack, and adds the restored view to the container.

screenUpdated

This method updates the action bar, options menu, and invokes the onNewScreen method when a new screen is displayed.

onNewScreen

This method is called when a new screen is displayed. Subclasses of NavigatingActivity can override this method to provide their own implementation.

onCreateOptionsMenu

This method is called to create the options menu. It invokes the onCreateOptionsMenu lambda passed to the NO_MENU property.

onOptionsItemSelected

This method is called when an options menu item is selected. If the home button is selected, it calls onBackPressed. Otherwise, it calls the superclass implementation.

onDestroy

This method is called when the activity is destroyed. It notifies the current view that the screen is exiting.

Companion Object

NO_MENU

A lambda function that does nothing. It can be used as the default value for the onCreateOptionsMenu property.

package leakcanary.internal.navigation

import android.app.Activity
import android.content.Intent
import android.os.Build.VERSION.SDK_INT
import android.os.Bundle
import android.os.Parcelable
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.view.animation.AnimationUtils.loadAnimation
import com.squareup.leakcanary.core.R

/**
 * A simple backstack navigating activity
 */
internal abstract class NavigatingActivity : Activity() {

  private lateinit var backstack: ArrayList<BackstackFrame>
  private lateinit var currentScreen: Screen

  private lateinit var container: ViewGroup
  private lateinit var currentView: View

  var onCreateOptionsMenu = NO_MENU

  fun installNavigation(
    savedInstanceState: Bundle?,
    container: ViewGroup
  ) {
    this.container = container

    if (savedInstanceState == null) {
      backstack = ArrayList()
      val screens = parseIntentScreens(intent)
      currentScreen = if (screens.isNotEmpty()) {
        screens.dropLast(1)
          .forEach { screen ->
            backstack.add(BackstackFrame(screen))
          }
        screens.last()
      } else {
        getLauncherScreen()
      }
    } else {
      currentScreen = savedInstanceState.getSerializable("currentScreen") as Screen
      @Suppress("UNCHECKED_CAST")
      backstack = savedInstanceState.getParcelableArrayList<Parcelable>(
        "backstack"
      ) as ArrayList<BackstackFrame>
    }
    currentView = currentScreen.createView(container)
    container.addView(currentView)

    actionBar?.run {
      setHomeButtonEnabled(true)
      setDisplayHomeAsUpEnabled(true)
    }
    screenUpdated()
  }

  override fun onNewIntent(intent: Intent) {
    val screens = parseIntentScreens(intent)
    if (screens.isNotEmpty()) {
      backstack.clear()
      screens.dropLast(1)
        .forEach { screen ->
          backstack.add(BackstackFrame(screen))
        }
      goTo(screens.last())
    }
  }

  abstract fun parseIntentScreens(intent: Intent): List<Screen>

  open fun getLauncherScreen(): Screen {
    TODO("Launcher activities should override getLauncherScreen()")
  }

  public override fun onSaveInstanceState(outState: Bundle) {
    super.onSaveInstanceState(outState)
    outState.putSerializable("currentScreen", currentScreen)
    outState.putParcelableArrayList("backstack", backstack)
  }

  override fun onBackPressed() {
    if (backstack.size > 0) {
      goBack()
      return
    }
    super.onBackPressed()
  }

  fun resetTo(screen: Screen) {
    onCreateOptionsMenu = NO_MENU

    currentView.startAnimation(loadAnimation(this, R.anim.leak_canary_exit_alpha))
    container.removeView(currentView)
    currentView.notifyScreenExiting()

    backstack.clear()

    currentScreen = screen
    currentView = currentScreen.createView(container)
    currentView.startAnimation(loadAnimation(this, R.anim.leak_canary_enter_alpha))
    container.addView(currentView)

    screenUpdated()
  }

  fun goTo(screen: Screen) {
    onCreateOptionsMenu = NO_MENU

    currentView.startAnimation(loadAnimation(this, R.anim.leak_canary_exit_forward))
    container.removeView(currentView)
    currentView.notifyScreenExiting()
    val backstackFrame = BackstackFrame(currentScreen, currentView)
    backstack.add(backstackFrame)

    currentScreen = screen
    currentView = currentScreen.createView(container)
    currentView.startAnimation(loadAnimation(this, R.anim.leak_canary_enter_forward))
    container.addView(currentView)

    screenUpdated()
  }

  fun refreshCurrentScreen() {
    onCreateOptionsMenu = NO_MENU
    container.removeView(currentView)
    currentView.notifyScreenExiting()
    currentView = currentScreen.createView(container)
    container.addView(currentView)

    screenUpdated()
  }

  fun goBack() {
    onCreateOptionsMenu = NO_MENU

    currentView.startAnimation(loadAnimation(this, R.anim.leak_canary_exit_backward))
    container.removeView(currentView)
    currentView.notifyScreenExiting()

    val latest = backstack.removeAt(backstack.size - 1)
    currentScreen = latest.screen
    currentView = currentScreen.createView(container)
    currentView.startAnimation(loadAnimation(this, R.anim.leak_canary_enter_backward))
    container.addView(currentView, 0)
    latest.restore(currentView)

    screenUpdated()
  }

  private fun screenUpdated() {
    invalidateOptionsMenu()
    if (SDK_INT >= 18) {
      actionBar?.run {
        val goBack = backstack.size > 0
        val indicator = if (goBack) 0 else android.R.drawable.ic_menu_close_clear_cancel
        setHomeAsUpIndicator(indicator)
      }
    }
    onNewScreen(currentScreen)
  }

  protected open fun onNewScreen(screen: Screen) {
  }

  override fun onCreateOptionsMenu(menu: Menu): Boolean {
    onCreateOptionsMenu.invoke(menu)
    return true
  }

  override fun onOptionsItemSelected(item: MenuItem): Boolean =
    when (item.itemId) {
      android.R.id.home -> {
        onBackPressed()
        true
      }
      else -> super.onOptionsItemSelected(item)
    }

  override fun onDestroy() {
    super.onDestroy()
    currentView.notifyScreenExiting()
  }

  companion object {
    val NO_MENU: ((Menu) -> Unit) = {}
  }
}