diff --git a/JankStatsSample/app/build.gradle.kts b/JankStatsSample/app/build.gradle.kts
index b89c70bd..e007793f 100644
--- a/JankStatsSample/app/build.gradle.kts
+++ b/JankStatsSample/app/build.gradle.kts
@@ -18,20 +18,20 @@ plugins {
id("com.android.application")
alias(libs.plugins.kotlin)
alias(libs.plugins.compose.compiler)
+ alias(libs.plugins.kotlin.serialization)
}
android {
- compileSdk = 35
+ compileSdk = 36
buildFeatures {
- viewBinding = true
compose = true
}
defaultConfig {
applicationId = "com.example.jankstats"
- minSdk = 21
- targetSdk = 35
+ minSdk = 23
+ targetSdk = 36
versionCode = 1
versionName = "1.0"
@@ -40,17 +40,16 @@ android {
buildTypes {
getByName("release") {
- isMinifyEnabled = false
+ isMinifyEnabled = true
+ isShrinkResources = true
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"))
}
}
compileOptions {
- sourceCompatibility = JavaVersion.VERSION_11
- targetCompatibility = JavaVersion.VERSION_11
- }
- kotlinOptions {
- jvmTarget = "11"
+ sourceCompatibility = JavaVersion.VERSION_21
+ targetCompatibility = JavaVersion.VERSION_21
}
+
composeOptions {
kotlinCompilerExtensionVersion = "1.5.15"
}
@@ -62,13 +61,15 @@ dependencies {
implementation(composeBom)
implementation(libs.activity)
implementation(libs.appcompat)
- implementation(libs.compose.material)
+ implementation(libs.compose.activity)
+ implementation(libs.material.icons.core)
+ implementation(libs.material3)
implementation(libs.compose.ui)
implementation(libs.compose.ui.tooling)
- implementation(libs.constraintlayout)
implementation(libs.jankstats)
implementation(libs.material)
- implementation(libs.navigation.fragment)
- implementation(libs.navigation.ui)
+ implementation(libs.navigation3.runtime)
+ implementation(libs.navigation3.ui)
+ implementation(libs.kotlinx.serialization.json)
implementation(libs.tracing)
}
diff --git a/JankStatsSample/app/src/main/AndroidManifest.xml b/JankStatsSample/app/src/main/AndroidManifest.xml
index a9dcf24f..3dcb94df 100644
--- a/JankStatsSample/app/src/main/AndroidManifest.xml
+++ b/JankStatsSample/app/src/main/AndroidManifest.xml
@@ -11,7 +11,8 @@
+ android:exported="true"
+ android:windowSoftInputMode="adjustResize">
@@ -19,7 +20,8 @@
+ android:exported="true"
+ android:windowSoftInputMode="adjustResize">
diff --git a/JankStatsSample/app/src/main/java/com/example/jankstats/JankAggregatorActivity.kt b/JankStatsSample/app/src/main/java/com/example/jankstats/JankAggregatorActivity.kt
index eeb59b82..198d0095 100644
--- a/JankStatsSample/app/src/main/java/com/example/jankstats/JankAggregatorActivity.kt
+++ b/JankStatsSample/app/src/main/java/com/example/jankstats/JankAggregatorActivity.kt
@@ -18,14 +18,30 @@ package com.example.jankstats
import android.os.Bundle
import android.util.Log
-import androidx.appcompat.app.AppCompatActivity
-import androidx.metrics.performance.PerformanceMetricsState
-import androidx.navigation.NavController
-import androidx.navigation.fragment.NavHostFragment
-import androidx.navigation.ui.AppBarConfiguration
-import androidx.navigation.ui.navigateUp
-import androidx.navigation.ui.setupActionBarWithNavController
-import com.example.jankstats.databinding.ActivityJankLoggingBinding
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.activity.enableEdgeToEdge
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.rememberLazyListState
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.snapshotFlow
+import androidx.compose.ui.Modifier
+import androidx.lifecycle.compose.LifecycleResumeEffect
+import androidx.navigation3.runtime.NavKey
+import androidx.navigation3.runtime.entryProvider
+import androidx.navigation3.ui.NavDisplay
+import com.example.jankstats.compose.JankStatsScaffold
+import com.example.jankstats.compose.JankStatsTheme
+import com.example.jankstats.compose.MessageContentScreen
+import com.example.jankstats.compose.MessageList
+import com.example.jankstats.compose.rememberMetricsStateHolder
+import com.example.jankstats.navigation.ComposeListRoute
+import com.example.jankstats.navigation.MessageContentRoute
+import com.example.jankstats.navigation.MessageListRoute
+import com.example.jankstats.navigation.Navigator
+import com.example.jankstats.navigation.rememberNavigationState
+import com.example.jankstats.navigation.toEntries
/**
* This activity shows how to use JankStatsAggregator, a class in this test directory layered
@@ -35,15 +51,10 @@ import com.example.jankstats.databinding.ActivityJankLoggingBinding
* or if JankStatsAggregator issues the report itself.
*/
// [START aggregator_activity_init]
-class JankAggregatorActivity : AppCompatActivity() {
+class JankAggregatorActivity : ComponentActivity() {
private lateinit var jankStatsAggregator: JankStatsAggregator
- // [START_EXCLUDE silent]
- private lateinit var binding: ActivityJankLoggingBinding
- private lateinit var navController: NavController
- private lateinit var appBarConfiguration: AppBarConfiguration
-
// [START jank_aggregator_listener]
private val jankReportListener =
JankStatsAggregator.OnJankReportListener { reason, totalFrames, jankFrameData ->
@@ -65,48 +76,90 @@ class JankAggregatorActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- // [START_EXCLUDE]
- binding = ActivityJankLoggingBinding.inflate(layoutInflater)
- setContentView(binding.root)
- setupUi()
- // [END_EXCLUDE]
- // Metrics state holder can be retrieved regardless of JankStats initialization.
- val metricsStateHolder = PerformanceMetricsState.getHolderForHierarchy(binding.root)
-
- // Initialize JankStats with an aggregator for the current window.
- jankStatsAggregator = JankStatsAggregator(window, jankReportListener)
-
- // Add the Activity name as state.
- metricsStateHolder.state?.putState("Activity", javaClass.simpleName)
- }
- // [END aggregator_activity_init]
+ enableEdgeToEdge()
+ setContent {
+ jankStatsAggregator = remember {
+ JankStatsAggregator(window, jankReportListener)
+ }
+ // ...
+ // metrics state holder can be retrieved regardless of JankStats initialization
+ val metricsStateHolder = rememberMetricsStateHolder()
+ // ...
+ // add activity name as state
+ metricsStateHolder.state?.putState("Activity", javaClass.simpleName)
+
+ LifecycleResumeEffect(jankStatsAggregator) {
+ jankStatsAggregator.jankStats.isTrackingEnabled = true
+ onPauseOrDispose {
+ jankStatsAggregator.issueJankReport("Activity paused")
+ jankStatsAggregator.jankStats.isTrackingEnabled = false
+ }
+ }
- // [START aggregator_tracking_enabled]
- override fun onResume() {
- super.onResume()
- jankStatsAggregator.jankStats.isTrackingEnabled = true
- }
+ val listState = rememberLazyListState()
+ LaunchedEffect(metricsStateHolder, listState) {
+ snapshotFlow { listState.isScrollInProgress }.collect { isScrolling ->
+ if (isScrolling) {
+ metricsStateHolder.state?.putState("LazyList", "Scrolling")
+ } else {
+ metricsStateHolder.state?.removeState("LazyList")
+ }
+ }
+ }
- override fun onPause() {
- super.onPause()
- // Before disabling tracking, issue the report with (optionally) specified reason.
- jankStatsAggregator.issueJankReport("Activity paused")
- jankStatsAggregator.jankStats.isTrackingEnabled = false
- }
- // [END aggregator_tracking_enabled]
+ val topLevelRoutes = remember { setOf(MessageListRoute, ComposeListRoute) }
+ val navigationState = rememberNavigationState(
+ startRoute = MessageListRoute,
+ topLevelRoutes = topLevelRoutes
+ )
+ val navigator = remember(navigationState) { Navigator(navigationState) }
- override fun onSupportNavigateUp(): Boolean {
- return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
- }
+ val currentStack = navigationState.backStacks[navigationState.topLevelRoute]
+ val activeKey = currentStack?.lastOrNull() ?: navigationState.topLevelRoute
+ val canNavigateUp = currentStack != null && currentStack.size > 1
- private fun setupUi() {
- setSupportActionBar(binding.toolbar)
+ val title = when (activeKey) {
+ is MessageListRoute -> "Message List"
+ is ComposeListRoute -> "Compose List"
+ is MessageContentRoute -> "Message Content"
+ else -> "JankStats Sample"
+ }
- val navHostFragment =
- supportFragmentManager.findFragmentById(R.id.navigation_container) as NavHostFragment
- navController = navHostFragment.navController
+ val entryProvider = remember(navigator) {
+ entryProvider {
+ entry {
+ MessageList(onItemClick = { headerText ->
+ navigator.navigate(MessageContentRoute(headerText))
+ })
+ }
+ entry {
+ MessageList(onItemClick = { headerText ->
+ navigator.navigate(MessageContentRoute(headerText))
+ })
+ }
+ entry { route ->
+ MessageContentScreen(title = route.title)
+ }
+ }
+ }
- appBarConfiguration = AppBarConfiguration(navController.graph)
- setupActionBarWithNavController(navController, appBarConfiguration)
+ JankStatsTheme {
+ JankStatsScaffold(
+ title = title,
+ canNavigateUp = canNavigateUp,
+ currentTopLevelRoute = navigationState.topLevelRoute,
+ onNavigateUp = { navigator.goBack() },
+ onBottomTabSelected = { navKey ->
+ navigator.navigate(navKey)
+ }
+ ) { innerPadding ->
+ NavDisplay(
+ entries = navigationState.toEntries(entryProvider),
+ onBack = { navigator.goBack() },
+ modifier = Modifier.padding(innerPadding)
+ )
+ }
+ }
+ }
}
}
diff --git a/JankStatsSample/app/src/main/java/com/example/jankstats/JankLoggingActivity.kt b/JankStatsSample/app/src/main/java/com/example/jankstats/JankLoggingActivity.kt
index 797fe3ae..6588dfe2 100644
--- a/JankStatsSample/app/src/main/java/com/example/jankstats/JankLoggingActivity.kt
+++ b/JankStatsSample/app/src/main/java/com/example/jankstats/JankLoggingActivity.kt
@@ -18,33 +18,38 @@ package com.example.jankstats
import android.os.Bundle
import android.util.Log
-import androidx.appcompat.app.AppCompatActivity
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.activity.enableEdgeToEdge
+import androidx.compose.foundation.layout.padding
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.lifecycle.compose.LifecycleResumeEffect
import androidx.metrics.performance.JankStats
-import androidx.metrics.performance.PerformanceMetricsState
-import androidx.navigation.NavController
-import androidx.navigation.fragment.NavHostFragment
-import androidx.navigation.ui.AppBarConfiguration
-import androidx.navigation.ui.navigateUp
-import androidx.navigation.ui.setupActionBarWithNavController
-import androidx.navigation.ui.setupWithNavController
-import com.example.jankstats.databinding.ActivityJankLoggingBinding
+import androidx.navigation3.runtime.NavKey
+import androidx.navigation3.runtime.entryProvider
+import androidx.navigation3.ui.NavDisplay
+import com.example.jankstats.compose.JankStatsScaffold
+import com.example.jankstats.compose.JankStatsTheme
+import com.example.jankstats.compose.MessageContentScreen
+import com.example.jankstats.compose.MessageList
+import com.example.jankstats.compose.rememberMetricsStateHolder
+import com.example.jankstats.navigation.ComposeListRoute
+import com.example.jankstats.navigation.MessageContentRoute
+import com.example.jankstats.navigation.MessageListRoute
+import com.example.jankstats.navigation.Navigator
+import com.example.jankstats.navigation.rememberNavigationState
+import com.example.jankstats.navigation.toEntries
/**
* This activity shows the basic usage of JankStats, from creating and enabling it to track
* a view hierarchy, to setting application state on JankStats, to receiving and logging per-frame
* callbacks with jank data.
*/
-// [START activity_init]
-class JankLoggingActivity : AppCompatActivity() {
+class JankLoggingActivity : ComponentActivity() {
private lateinit var jankStats: JankStats
- // [START_EXCLUDE silent]
- private lateinit var binding: ActivityJankLoggingBinding
- private lateinit var navController: NavController
- private lateinit var appBarConfiguration: AppBarConfiguration
-
- // [START jank_frame_listener]
private val jankFrameListener = JankStats.OnFrameListener { frameData ->
// A real app could do something more interesting, like writing the info to local storage and later on report it.
Log.v("JankStatsSample", frameData.toString())
@@ -54,65 +59,81 @@ class JankLoggingActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- // [START_EXCLUDE]
- binding = ActivityJankLoggingBinding.inflate(layoutInflater)
- setContentView(binding.root)
- setupUi()
- // [END_EXCLUDE]
- // metrics state holder can be retrieved regardless of JankStats initialization
- val metricsStateHolder = PerformanceMetricsState.getHolderForHierarchy(binding.root)
-
- // initialize JankStats for current window
- jankStats = JankStats.createAndTrack(window, jankFrameListener)
-
- // add activity name as state
- metricsStateHolder.state?.putState("Activity", javaClass.simpleName)
- // [START_EXCLUDE]
- setupNavigationState()
- // [END_EXCLUDE]
- }
- // [END activity_init]
-
- // [START tracking_enabled]
- override fun onResume() {
- super.onResume()
- jankStats.isTrackingEnabled = true
- }
-
- override fun onPause() {
- super.onPause()
- jankStats.isTrackingEnabled = false
- }
- // [END tracking_enabled]
-
- override fun onSupportNavigateUp(): Boolean {
- return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
- }
-
- private fun setupUi() {
- setSupportActionBar(binding.toolbar)
-
- val navHostFragment =
- supportFragmentManager.findFragmentById(R.id.navigation_container) as NavHostFragment
- navController = navHostFragment.navController
-
- binding.bottomNavigation.setupWithNavController(navController)
-
- appBarConfiguration = AppBarConfiguration(navController.graph)
-
- setupActionBarWithNavController(navController, appBarConfiguration)
- }
-
- private fun setupNavigationState() {
- // [START state_navigation]
- val metricsStateHolder = PerformanceMetricsState.getHolderForHierarchy(binding.root)
- // add current navigation information into JankStats state
- navController.addOnDestinationChangedListener { _, destination, arguments ->
- metricsStateHolder.state?.putState(
- "Navigation",
- "Args(${arguments.toString()}), $destination"
+ enableEdgeToEdge()
+
+ setContent {
+ jankStats = remember {
+ JankStats.createAndTrack(window, jankFrameListener)
+ }
+ // ...
+ // metrics state holder can be retrieved regardless of JankStats initialization
+ val metricsStateHolder = rememberMetricsStateHolder()
+ // ...
+ // ...
+ // add activity name as state
+ metricsStateHolder.state?.putState("Activity", javaClass.simpleName)
+
+ LifecycleResumeEffect(jankStats) {
+ jankStats.isTrackingEnabled = true
+ onPauseOrDispose {
+ Log.v("Activity Paused,","Tracking jank stats is disabled")
+ jankStats.isTrackingEnabled = false
+ }
+ }
+
+ val topLevelRoutes = remember { setOf(MessageListRoute, ComposeListRoute) }
+ val navigationState = rememberNavigationState(
+ startRoute = MessageListRoute,
+ topLevelRoutes = topLevelRoutes
)
+ val navigator = remember(navigationState) { Navigator(navigationState) }
+
+ val currentStack = navigationState.backStacks[navigationState.topLevelRoute]
+ val activeKey = currentStack?.lastOrNull() ?: navigationState.topLevelRoute
+ val canNavigateUp = currentStack != null && currentStack.size > 1
+
+ val title = when (activeKey) {
+ is MessageListRoute -> "Message List"
+ is ComposeListRoute -> "Compose List"
+ is MessageContentRoute -> "Message Content"
+ else -> "JankStats Sample"
+ }
+
+ val entryProvider = remember(navigator) {
+ entryProvider {
+ entry {
+ MessageList(onItemClick = { headerText ->
+ navigator.navigate(MessageContentRoute(headerText))
+ })
+ }
+ entry {
+ MessageList(onItemClick = { headerText ->
+ navigator.navigate(MessageContentRoute(headerText))
+ })
+ }
+ entry { route ->
+ MessageContentScreen(title = route.title)
+ }
+ }
+ }
+
+ JankStatsTheme {
+ JankStatsScaffold(
+ title = title,
+ canNavigateUp = canNavigateUp,
+ currentTopLevelRoute = navigationState.topLevelRoute,
+ onNavigateUp = { navigator.goBack() },
+ onBottomTabSelected = { navKey ->
+ navigator.navigate(navKey)
+ }
+ ) { innerPadding ->
+ NavDisplay(
+ entries = navigationState.toEntries(entryProvider),
+ onBack = { navigator.goBack() },
+ modifier = Modifier.padding(innerPadding)
+ )
+ }
+ }
}
- // [END state_navigation]
}
}
diff --git a/JankStatsSample/app/src/main/java/com/example/jankstats/JankyView.kt b/JankStatsSample/app/src/main/java/com/example/jankstats/JankyView.kt
deleted file mode 100644
index 08de49cb..00000000
--- a/JankStatsSample/app/src/main/java/com/example/jankstats/JankyView.kt
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.example.jankstats
-
-import android.content.Context
-import android.graphics.Canvas
-import android.util.AttributeSet
-import android.view.View
-import com.example.jankstats.tools.simulateJank
-
-/**
- * This custom view is used to inject an artificial, random delay during drawing, to simulate
- * jank on the UI thread.
- */
-class JankyView @JvmOverloads constructor(
- context: Context,
- attrs: AttributeSet? = null,
- defStyleAttr: Int = 0,
- defStyleRes: Int = 0
-) : View(context, attrs, defStyleAttr, defStyleRes) {
-
- override fun onDraw(canvas: Canvas) {
- simulateJank()
- super.onDraw(canvas)
- }
-
-}
diff --git a/JankStatsSample/app/src/main/java/com/example/jankstats/MessageContentFragment.kt b/JankStatsSample/app/src/main/java/com/example/jankstats/MessageContentFragment.kt
deleted file mode 100644
index 329a24a2..00000000
--- a/JankStatsSample/app/src/main/java/com/example/jankstats/MessageContentFragment.kt
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.example.jankstats
-
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import androidx.fragment.app.Fragment
-import com.example.jankstats.databinding.FragmentMessageContentBinding
-
-/**
- * A simple [Fragment] subclass.
- */
-class MessageContentFragment : Fragment() {
-
- private var _binding: FragmentMessageContentBinding? = null
- private val binding get() = requireNotNull(_binding)
-
- override fun onCreateView(
- inflater: LayoutInflater,
- container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View {
- // Inflate the layout for this fragment
- _binding = FragmentMessageContentBinding.inflate(inflater)
- return binding.root
- }
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- binding.messageHeader.text = arguments?.getString("title")
- }
-
- override fun onDestroyView() {
- _binding = null
- super.onDestroyView()
- }
-}
diff --git a/JankStatsSample/app/src/main/java/com/example/jankstats/MessageListAdapter.kt b/JankStatsSample/app/src/main/java/com/example/jankstats/MessageListAdapter.kt
deleted file mode 100644
index 6aa4c0a0..00000000
--- a/JankStatsSample/app/src/main/java/com/example/jankstats/MessageListAdapter.kt
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.example.jankstats
-
-import android.view.LayoutInflater
-import android.view.ViewGroup
-import androidx.core.os.bundleOf
-import androidx.navigation.Navigation
-import androidx.recyclerview.widget.RecyclerView
-import com.example.jankstats.databinding.MessageItemBinding
-
-class MessageListAdapter(
- private val messageList: Array
-) : RecyclerView.Adapter() {
-
- class MessageHeaderViewHolder(
- private val binding: MessageItemBinding
- ) : RecyclerView.ViewHolder(binding.root) {
-
- fun bind(headerText: String) {
- itemView.setOnClickListener {
- val bundle = bundleOf("title" to headerText)
- Navigation.findNavController(it).navigate(
- R.id.action_messageList_to_messageContent, bundle
- )
- }
- binding.messageHeader.text = headerText
- }
- }
-
- override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MessageHeaderViewHolder {
- val inflater = LayoutInflater.from(parent.context)
- val itemBinding = MessageItemBinding.inflate(inflater, parent, false)
- return MessageHeaderViewHolder(itemBinding)
- }
-
- override fun onBindViewHolder(holder: MessageHeaderViewHolder, position: Int) {
- holder.bind(messageList[position])
- }
-
- override fun getItemCount(): Int {
- return messageList.size
- }
-}
diff --git a/JankStatsSample/app/src/main/java/com/example/jankstats/MessageListFragment.kt b/JankStatsSample/app/src/main/java/com/example/jankstats/MessageListFragment.kt
deleted file mode 100644
index b706b941..00000000
--- a/JankStatsSample/app/src/main/java/com/example/jankstats/MessageListFragment.kt
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.example.jankstats
-
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import androidx.fragment.app.Fragment
-import androidx.metrics.performance.PerformanceMetricsState
-import androidx.recyclerview.widget.RecyclerView
-import com.example.jankstats.databinding.FragmentMessageListBinding
-
-/**
- * A simple [Fragment] subclass as the second destination in the navigation.
- */
-class MessageListFragment : Fragment() {
-
- private var _binding: FragmentMessageListBinding? = null
- private val binding get() = requireNotNull(_binding)
-
- // initialized when View is created
- private var metricsStateHolder: PerformanceMetricsState.Holder? = null
-
- private val messageList = Array(100) { "Message #$it" }
-
- // [START state_recyclerview]
- private val scrollListener = object : RecyclerView.OnScrollListener() {
- override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
- // check if JankStats is initialized and skip adding state if not
- val metricsState = metricsStateHolder?.state ?: return
-
- when (newState) {
- RecyclerView.SCROLL_STATE_DRAGGING -> {
- metricsState.putState("RecyclerView", "Dragging")
- }
- RecyclerView.SCROLL_STATE_SETTLING -> {
- metricsState.putState("RecyclerView", "Settling")
- }
- else -> {
- metricsState.removeState("RecyclerView")
- }
- }
- }
- }
- // [END state_recyclerview]
-
- override fun onCreateView(
- inflater: LayoutInflater,
- container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View {
- _binding = FragmentMessageListBinding.inflate(inflater)
- return binding.root
- }
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- metricsStateHolder = PerformanceMetricsState.getHolderForHierarchy(view)
-
- binding.messageList.adapter = MessageListAdapter(messageList)
- binding.messageList.addOnScrollListener(scrollListener)
- }
-
- override fun onDestroyView() {
- _binding = null
- metricsStateHolder = null
- super.onDestroyView()
- }
-
-}
diff --git a/JankStatsSample/app/src/main/java/com/example/jankstats/compose/JankStatsScaffold.kt b/JankStatsSample/app/src/main/java/com/example/jankstats/compose/JankStatsScaffold.kt
new file mode 100644
index 00000000..8b7aa0bf
--- /dev/null
+++ b/JankStatsSample/app/src/main/java/com/example/jankstats/compose/JankStatsScaffold.kt
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2026 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.jankstats.compose
+
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.NavigationBar
+import androidx.compose.material3.NavigationBarItem
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBar
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.navigation3.runtime.NavKey
+import com.example.jankstats.R
+import com.example.jankstats.navigation.ComposeListRoute
+import com.example.jankstats.navigation.MessageListRoute
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun JankStatsScaffold(
+ title: String,
+ canNavigateUp: Boolean,
+ currentTopLevelRoute: NavKey?,
+ onNavigateUp: () -> Unit,
+ onBottomTabSelected: (NavKey) -> Unit,
+ content: @Composable (PaddingValues) -> Unit
+) {
+ Scaffold(
+ topBar = {
+ TopAppBar(
+ title = { Text(text = title) },
+ navigationIcon = {
+ if (canNavigateUp) {
+ IconButton(onClick = onNavigateUp) {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_arrow_back),
+ contentDescription = "Navigate Up"
+ )
+ }
+ }
+ }
+ )
+ },
+ bottomBar = {
+ NavigationBar {
+ NavigationBarItem(
+ selected = currentTopLevelRoute == MessageListRoute,
+ onClick = { onBottomTabSelected(MessageListRoute) },
+ icon = {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_views),
+ contentDescription = stringResource(id = R.string.views)
+ )
+ },
+ label = { Text(text = stringResource(id = R.string.views)) }
+ )
+ NavigationBarItem(
+ selected = currentTopLevelRoute == ComposeListRoute,
+ onClick = { onBottomTabSelected(ComposeListRoute) },
+ icon = {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_compose),
+ contentDescription = stringResource(id = R.string.compose)
+ )
+ },
+ label = { Text(text = stringResource(id = R.string.compose)) }
+ )
+ NavigationBarItem(
+ selected = false,
+ onClick = { /* No-op / Not implemented */ },
+ icon = {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_construction),
+ contentDescription = stringResource(id = R.string.not_implemented)
+ )
+ },
+ label = { Text(text = stringResource(id = R.string.not_implemented)) }
+ )
+ }
+ }
+ ) { innerPadding ->
+ content(innerPadding)
+ }
+}
diff --git a/JankStatsSample/app/src/main/java/com/example/jankstats/compose/JankStatsTheme.kt b/JankStatsSample/app/src/main/java/com/example/jankstats/compose/JankStatsTheme.kt
new file mode 100644
index 00000000..53491dc4
--- /dev/null
+++ b/JankStatsSample/app/src/main/java/com/example/jankstats/compose/JankStatsTheme.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2026 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.jankstats.compose
+
+import android.os.Build
+import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.darkColorScheme
+import androidx.compose.material3.dynamicDarkColorScheme
+import androidx.compose.material3.dynamicLightColorScheme
+import androidx.compose.material3.lightColorScheme
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalContext
+
+private val Purple80 = Color(0xFFD0BCFF)
+private val PurpleGrey80 = Color(0xFFCCC2DC)
+private val Pink80 = Color(0xFFEFB8C8)
+
+private val Purple40 = Color(0xFF6650a4)
+private val PurpleGrey40 = Color(0xFF625b71)
+private val Pink40 = Color(0xFF7D5260)
+
+private val DarkColorScheme = darkColorScheme(
+ primary = Purple80,
+ secondary = PurpleGrey80,
+ tertiary = Pink80
+)
+
+private val LightColorScheme = lightColorScheme(
+ primary = Purple40,
+ secondary = PurpleGrey40,
+ tertiary = Pink40
+)
+
+@Composable
+fun JankStatsTheme(
+ darkTheme: Boolean = isSystemInDarkTheme(),
+ dynamicColor: Boolean = true,
+ content: @Composable () -> Unit
+) {
+ val colorScheme = when {
+ dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
+ val context = LocalContext.current
+ if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
+ }
+
+ darkTheme -> DarkColorScheme
+ else -> LightColorScheme
+ }
+
+ MaterialTheme(
+ colorScheme = colorScheme,
+ content = content
+ )
+}
diff --git a/JankStatsSample/app/src/main/java/com/example/jankstats/compose/MessageContentScreen.kt b/JankStatsSample/app/src/main/java/com/example/jankstats/compose/MessageContentScreen.kt
new file mode 100644
index 00000000..590f7f2f
--- /dev/null
+++ b/JankStatsSample/app/src/main/java/com/example/jankstats/compose/MessageContentScreen.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2026 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.jankstats.compose
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+
+@Composable
+fun MessageContentScreen(title: String?) {
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(16.dp)
+ ) {
+ Text(
+ text = title ?: "Hello blank screen",
+ fontSize = 24.sp,
+ fontWeight = FontWeight.Bold,
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(bottom = 16.dp)
+ )
+ Text(
+ text = "Message content placeholder",
+ modifier = Modifier.fillMaxWidth()
+ )
+ }
+}
+
+@Preview(showBackground = true)
+@Composable
+fun MessageContentScreenPreview() {
+ JankStatsTheme {
+ MessageContentScreen(title = "Message #1")
+ }
+}
diff --git a/JankStatsSample/app/src/main/java/com/example/jankstats/compose/ComposeListFragment.kt b/JankStatsSample/app/src/main/java/com/example/jankstats/compose/MessageListScreen.kt
similarity index 61%
rename from JankStatsSample/app/src/main/java/com/example/jankstats/compose/ComposeListFragment.kt
rename to JankStatsSample/app/src/main/java/com/example/jankstats/compose/MessageListScreen.kt
index 3b3cb4d8..26d7fdb6 100644
--- a/JankStatsSample/app/src/main/java/com/example/jankstats/compose/ComposeListFragment.kt
+++ b/JankStatsSample/app/src/main/java/com/example/jankstats/compose/MessageListScreen.kt
@@ -16,59 +16,33 @@
package com.example.jankstats.compose
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
-import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.rememberLazyListState
-import androidx.compose.material.MaterialTheme
-import androidx.compose.material.Text
+import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.platform.ComposeView
-import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
-import androidx.fragment.app.Fragment
-import androidx.navigation.findNavController
-import com.example.jankstats.R
import com.example.jankstats.tools.simulateJank
-/**
- * Showcase how to work with JankStats from Compose.
- * This Fragment will intentionally cause poor UI performance which can be monitored by JankStats.
- */
-class ComposeListFragment : Fragment() {
-
- override fun onCreateView(
- inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
- ): View {
- return ComposeView(requireContext()).apply {
- setViewCompositionStrategy(
- ViewCompositionStrategy.DisposeOnLifecycleDestroyed(viewLifecycleOwner)
- )
-
- setContent {
- MaterialTheme {
- MessageList(onItemClick = {
- findNavController().navigate(R.id.action_composeList_to_messageContent)
- })
- }
- }
- }
- }
-}
-
@Composable
-fun MessageList(onItemClick: () -> Unit) {
+fun MessageList(
+ onItemClick: (String) -> Unit,
+ contentPadding: PaddingValues = PaddingValues(0.dp)
+) {
val listState = rememberLazyListState()
// [START compose_jank_metrics]
val metricsStateHolder = rememberMetricsStateHolder()
@@ -85,25 +59,27 @@ fun MessageList(onItemClick: () -> Unit) {
}
// [END compose_jank_metrics]
-
- LazyColumn(state = listState) {
+ LazyColumn(
+ state = listState,
+ contentPadding = contentPadding
+ ) {
items(100) { index ->
MessageItem(index, onItemClick)
}
}
}
-
@Composable
-fun MessageItem(item: Int, onItemClick: () -> Unit) {
+fun MessageItem(item: Int, onItemClick: (String) -> Unit) {
+ val headerText = "Message #$item"
Row(
Modifier
.fillMaxWidth()
- .clickable(onClick = onItemClick)
+ .clickable(onClick = { onItemClick(headerText) })
.padding(16.dp),
verticalAlignment = Alignment.CenterVertically
) {
- Text("Message #$item", Modifier.padding(end = 16.dp))
+ Text(headerText, Modifier.padding(end = 16.dp))
JankyComposable()
}
}
@@ -123,5 +99,7 @@ fun JankyComposable() {
@Preview(widthDp = 500)
@Composable
fun MessageListPreview() {
- MessageList(onItemClick = {})
+ JankStatsTheme {
+ MessageList(onItemClick = {})
+ }
}
diff --git a/JankStatsSample/app/src/main/java/com/example/jankstats/navigation/NavKeys.kt b/JankStatsSample/app/src/main/java/com/example/jankstats/navigation/NavKeys.kt
new file mode 100644
index 00000000..a3f07c9c
--- /dev/null
+++ b/JankStatsSample/app/src/main/java/com/example/jankstats/navigation/NavKeys.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2026 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.jankstats.navigation
+
+import androidx.navigation3.runtime.NavKey
+import kotlinx.serialization.Serializable
+
+@Serializable
+data object MessageListRoute : NavKey
+
+@Serializable
+data object ComposeListRoute : NavKey
+
+@Serializable
+data class MessageContentRoute(val title: String) : NavKey
diff --git a/JankStatsSample/app/src/main/java/com/example/jankstats/navigation/NavigationState.kt b/JankStatsSample/app/src/main/java/com/example/jankstats/navigation/NavigationState.kt
new file mode 100644
index 00000000..d7fceafe
--- /dev/null
+++ b/JankStatsSample/app/src/main/java/com/example/jankstats/navigation/NavigationState.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2026 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.jankstats.navigation
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSerializable
+import androidx.compose.runtime.setValue
+import androidx.compose.runtime.snapshots.SnapshotStateList
+import androidx.compose.runtime.toMutableStateList
+import androidx.navigation3.runtime.NavBackStack
+import androidx.navigation3.runtime.NavEntry
+import androidx.navigation3.runtime.NavKey
+import androidx.navigation3.runtime.rememberDecoratedNavEntries
+import androidx.navigation3.runtime.rememberNavBackStack
+import androidx.navigation3.runtime.rememberSaveableStateHolderNavEntryDecorator
+import androidx.navigation3.runtime.serialization.NavKeySerializer
+import androidx.savedstate.compose.serialization.serializers.MutableStateSerializer
+
+@Composable
+fun rememberNavigationState(
+ startRoute: NavKey,
+ topLevelRoutes: Set
+): NavigationState {
+
+ val topLevelRoute = rememberSerializable(
+ startRoute, topLevelRoutes,
+ serializer = MutableStateSerializer(NavKeySerializer())
+ ) {
+ mutableStateOf(startRoute)
+ }
+
+ val backStacks = topLevelRoutes.associateWith { key -> rememberNavBackStack(key) }
+
+ return remember(startRoute, topLevelRoutes) {
+ NavigationState(
+ startRoute = startRoute,
+ topLevelRoute = topLevelRoute,
+ backStacks = backStacks
+ )
+ }
+}
+
+class NavigationState(
+ val startRoute: NavKey,
+ topLevelRoute: MutableState,
+ val backStacks: Map>
+) {
+ var topLevelRoute: NavKey by topLevelRoute
+ val stacksInUse: List
+ get() = if (topLevelRoute == startRoute) {
+ listOf(startRoute)
+ } else {
+ listOf(startRoute, topLevelRoute)
+ }
+}
+
+@Composable
+fun NavigationState.toEntries(
+ entryProvider: (NavKey) -> NavEntry
+): SnapshotStateList> {
+
+ val decoratedEntries = backStacks.mapValues { (_, stack) ->
+ val decorators = listOf(
+ rememberSaveableStateHolderNavEntryDecorator(),
+ )
+ rememberDecoratedNavEntries(
+ backStack = stack,
+ entryDecorators = decorators,
+ entryProvider = entryProvider
+ )
+ }
+
+ return stacksInUse
+ .flatMap { decoratedEntries[it] ?: emptyList() }
+ .toMutableStateList()
+}
diff --git a/JankStatsSample/app/src/main/java/com/example/jankstats/navigation/Navigator.kt b/JankStatsSample/app/src/main/java/com/example/jankstats/navigation/Navigator.kt
new file mode 100644
index 00000000..7b1e26a6
--- /dev/null
+++ b/JankStatsSample/app/src/main/java/com/example/jankstats/navigation/Navigator.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2026 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.jankstats.navigation
+
+import androidx.navigation3.runtime.NavKey
+
+class Navigator(val state: NavigationState) {
+ fun navigate(route: NavKey) {
+ if (route in state.backStacks.keys) {
+ state.topLevelRoute = route
+ } else {
+ state.backStacks[state.topLevelRoute]?.add(route)
+ }
+ }
+
+ fun goBack() {
+ val currentStack = state.backStacks[state.topLevelRoute]
+ ?: error("Stack for ${state.topLevelRoute} not found")
+ val currentRoute = currentStack.last()
+
+ if (currentRoute == state.topLevelRoute) {
+ state.topLevelRoute = state.startRoute
+ } else {
+ currentStack.removeLastOrNull()
+ }
+ }
+}
diff --git a/JankStatsSample/app/src/main/res/drawable/ic_arrow_back.xml b/JankStatsSample/app/src/main/res/drawable/ic_arrow_back.xml
new file mode 100644
index 00000000..bab545a7
--- /dev/null
+++ b/JankStatsSample/app/src/main/res/drawable/ic_arrow_back.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/JankStatsSample/app/src/main/res/layout/activity_jank_logging.xml b/JankStatsSample/app/src/main/res/layout/activity_jank_logging.xml
deleted file mode 100644
index 75724d35..00000000
--- a/JankStatsSample/app/src/main/res/layout/activity_jank_logging.xml
+++ /dev/null
@@ -1,61 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/JankStatsSample/app/src/main/res/layout/fragment_message_content.xml b/JankStatsSample/app/src/main/res/layout/fragment_message_content.xml
deleted file mode 100644
index a99fe724..00000000
--- a/JankStatsSample/app/src/main/res/layout/fragment_message_content.xml
+++ /dev/null
@@ -1,44 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/JankStatsSample/app/src/main/res/layout/fragment_message_list.xml b/JankStatsSample/app/src/main/res/layout/fragment_message_list.xml
deleted file mode 100644
index 1d545e30..00000000
--- a/JankStatsSample/app/src/main/res/layout/fragment_message_list.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
-
-
-
diff --git a/JankStatsSample/app/src/main/res/layout/message_item.xml b/JankStatsSample/app/src/main/res/layout/message_item.xml
deleted file mode 100644
index 4363c15a..00000000
--- a/JankStatsSample/app/src/main/res/layout/message_item.xml
+++ /dev/null
@@ -1,38 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/JankStatsSample/app/src/main/res/navigation/nav_graph.xml b/JankStatsSample/app/src/main/res/navigation/nav_graph.xml
deleted file mode 100644
index 0f456b34..00000000
--- a/JankStatsSample/app/src/main/res/navigation/nav_graph.xml
+++ /dev/null
@@ -1,55 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/JankStatsSample/app/src/main/res/values/strings.xml b/JankStatsSample/app/src/main/res/values/strings.xml
index 7505a51b..51ec9701 100644
--- a/JankStatsSample/app/src/main/res/values/strings.xml
+++ b/JankStatsSample/app/src/main/res/values/strings.xml
@@ -17,16 +17,16 @@
JankStats
Settings
-
- First Fragment
- Second Fragment
+
+ First Screen
+ Second Screen
Next
Previous
- Hello first fragment
- Hello second fragment. Arg: %1$s
+ Hello first screen
+ Hello second screen. Arg: %1$s
- Hello blank fragment
+ Hello blank screen
Views
Compose
diff --git a/JankStatsSample/app/src/main/res/values/themes.xml b/JankStatsSample/app/src/main/res/values/themes.xml
index 60c47c0a..e54faa0b 100644
--- a/JankStatsSample/app/src/main/res/values/themes.xml
+++ b/JankStatsSample/app/src/main/res/values/themes.xml
@@ -1,20 +1,6 @@
-
+
diff --git a/JankStatsSample/build.gradle b/JankStatsSample/build.gradle
index 9cb821a6..7fe4686c 100644
--- a/JankStatsSample/build.gradle
+++ b/JankStatsSample/build.gradle
@@ -18,5 +18,5 @@ plugins {
alias(libs.plugins.application) apply false
alias(libs.plugins.kotlin) apply false
alias(libs.plugins.compose.compiler) apply false
-
-}
\ No newline at end of file
+ alias(libs.plugins.kotlin.serialization) apply false
+}
diff --git a/JankStatsSample/gradle/libs.versions.toml b/JankStatsSample/gradle/libs.versions.toml
index 3ff0f0a5..f430c764 100644
--- a/JankStatsSample/gradle/libs.versions.toml
+++ b/JankStatsSample/gradle/libs.versions.toml
@@ -14,17 +14,15 @@
[versions]
-agp = "8.11.1"
+agp = "8.11.2"
appcompat = "1.7.1"
-activity = "1.10.1"
-composeBom = "2025.07.00"
-coroutines = "1.10.2"
-constraint_layout = "2.2.1"
-core = "1.9.0"
-jankstats = "1.0.0-beta02"
-kotlin = "2.2.0"
-material = "1.12.0"
-navigation = "2.9.3"
+activity = "1.13.0"
+composeBom = "2026.06.01"
+jankstats = "1.0.0"
+kotlin = "2.4.10"
+kotlinxSerialization = "1.11.0"
+material = "1.14.0"
+nav3 = "1.1.5"
tracing = "1.3.0"
[libraries]
@@ -36,13 +34,13 @@ compose-activity = { group = "androidx.activity", name = "activity-compose" }
compose-material = { group = "androidx.compose.material", name = "material" }
compose-ui = { group = "androidx.compose.ui", name = "ui" }
compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling"}
-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraint_layout" }
jankstats = { group = "androidx.metrics", name = "metrics-performance", version.ref = "jankstats" }
-kotlin-coroutines = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "coroutines" }
-kotlin-stdlib = { group = "org.jetbrains.kotlin", name = "kotlin-stdlib", version.ref = "kotlin" }
+kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinxSerialization" }
material = { group = "com.google.android.material", name = "material", version.ref = "material"}
-navigation-fragment = { group = "androidx.navigation", name = "navigation-fragment-ktx", version.ref = "navigation"}
-navigation-ui = { group = "androidx.navigation", name = "navigation-ui-ktx", version.ref = "navigation"}
+material-icons-core = { module = "androidx.compose.material:material-icons-core" }
+material3 = { group = "androidx.compose.material3", name = "material3" }
+navigation3-runtime = { group = "androidx.navigation3", name = "navigation3-runtime", version.ref = "nav3" }
+navigation3-ui = { group = "androidx.navigation3", name = "navigation3-ui", version.ref = "nav3" }
tracing = { group = "androidx.tracing", name = "tracing-ktx", version.ref = "tracing"}
[plugins]
@@ -50,4 +48,4 @@ tracing = { group = "androidx.tracing", name = "tracing-ktx", version.ref = "tra
application = { id = "com.android.application", version.ref = "agp" }
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
kotlin = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
-test = { id = "com.android.test", version.ref = "agp" }
+kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }