1 /* <lambda>null2 * Copyright 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package androidx.compose.ui.tooling 18 19 import android.content.pm.ApplicationInfo 20 import android.os.Bundle 21 import android.util.Log 22 import androidx.activity.ComponentActivity 23 import androidx.activity.compose.setContent 24 import androidx.compose.foundation.layout.Box 25 import androidx.compose.foundation.layout.padding 26 import androidx.compose.material.ExtendedFloatingActionButton 27 import androidx.compose.material.Scaffold 28 import androidx.compose.material.Text 29 import androidx.compose.runtime.currentComposer 30 import androidx.compose.runtime.mutableIntStateOf 31 import androidx.compose.runtime.remember 32 import androidx.compose.ui.ExperimentalComposeUiApi 33 import androidx.compose.ui.Modifier 34 35 /** 36 * Activity used to run `@Composable` previews from Android Studio. 37 * 38 * The supported `@Composable` functions either have no parameters, or have only parameters with 39 * default values and/or *one* parameter annotated with `@PreviewParameter`. 40 * 41 * The `@Composable` fully qualified name must be passed to this Activity through intent parameters, 42 * using `composable` as the key. When deploying Compose Previews with `@PreviewParameter` annotated 43 * parameters, the provider should be specified as an intent parameter as well, using the key 44 * `parameterProviderClassName`. Optionally, `parameterProviderIndex` can also be set to display a 45 * specific provider value instead of all of them. 46 */ 47 @Suppress("ForbiddenSuperClass") 48 class PreviewActivity : ComponentActivity() { 49 50 private val TAG = "PreviewActivity" 51 52 override fun onCreate(savedInstanceState: Bundle?) { 53 super.onCreate(savedInstanceState) 54 if (applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE == 0) { 55 Log.d(TAG, "Application is not debuggable. Compose Preview not allowed.") 56 finish() 57 return 58 } 59 60 intent?.getStringExtra("composable")?.let { setComposableContent(it) } 61 } 62 63 @Suppress("DEPRECATION") 64 @OptIn(ExperimentalComposeUiApi::class) 65 private fun setComposableContent(composableFqn: String) { 66 Log.d(TAG, "PreviewActivity has composable $composableFqn") 67 val className = composableFqn.substringBeforeLast('.') 68 val methodName = composableFqn.substringAfterLast('.') 69 70 intent.getStringExtra("parameterProviderClassName")?.let { parameterProvider -> 71 setParameterizedContent(className, methodName, parameterProvider) 72 return@setComposableContent 73 } 74 Log.d(TAG, "Previewing '$methodName' without a parameter provider.") 75 setContent { ComposableInvoker.invokeComposable(className, methodName, currentComposer) } 76 } 77 78 /** 79 * Sets the activity content according to a given `@PreviewParameter` provider. If 80 * `parameterProviderIndex` is also set, the content will be a single `@Composable` that uses 81 * the `parameterProviderIndex`-th value in the provider's sequence as the argument value. 82 * Otherwise, the content will display a FAB that changes the argument value on click, cycling 83 * through all the values in the provider's sequence. 84 */ 85 @Suppress("DEPRECATION") 86 @OptIn(ExperimentalComposeUiApi::class) 87 private fun setParameterizedContent( 88 className: String, 89 methodName: String, 90 parameterProvider: String 91 ) { 92 Log.d(TAG, "Previewing '$methodName' with parameter provider: '$parameterProvider'") 93 val previewParameters = 94 getPreviewProviderParameters( 95 parameterProvider.asPreviewProviderClass(), 96 intent.getIntExtra("parameterProviderIndex", -1) 97 ) 98 99 // Handle the case where parameterProviderIndex is not provided. In this case, instead of 100 // showing an arbitrary value (e.g. the first one), we display a FAB that can be used to 101 // cycle through all the values. 102 if (previewParameters.size > 1) { 103 setContent { 104 val index = remember { mutableIntStateOf(0) } 105 106 Scaffold( 107 content = { padding -> 108 Box(Modifier.padding(padding)) { 109 ComposableInvoker.invokeComposable( 110 className, 111 methodName, 112 currentComposer, 113 previewParameters[index.intValue] 114 ) 115 } 116 }, 117 floatingActionButton = { 118 ExtendedFloatingActionButton( 119 text = { Text("Next") }, 120 onClick = { 121 index.intValue = (index.intValue + 1) % previewParameters.size 122 } 123 ) 124 } 125 ) 126 } 127 } else { 128 setContent { 129 ComposableInvoker.invokeComposable( 130 className, 131 methodName, 132 currentComposer, 133 *previewParameters 134 ) 135 } 136 } 137 } 138 } 139