1 /* <lambda>null2 * Copyright (C) 2024 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 com.android.internal.systemui.lint 18 19 import com.android.tools.lint.client.api.UElementHandler 20 import com.android.tools.lint.detector.api.Category 21 import com.android.tools.lint.detector.api.Detector 22 import com.android.tools.lint.detector.api.Implementation 23 import com.android.tools.lint.detector.api.Issue 24 import com.android.tools.lint.detector.api.JavaContext 25 import com.android.tools.lint.detector.api.Scope 26 import com.android.tools.lint.detector.api.Severity 27 import com.android.tools.lint.detector.api.SourceCodeScanner 28 import org.jetbrains.uast.UElement 29 import org.jetbrains.uast.UFile 30 import org.jetbrains.uast.UImportStatement 31 32 class CollectAsStateDetector : Detector(), SourceCodeScanner { 33 34 override fun getApplicableUastTypes(): List<Class<out UElement>> { 35 return listOf(UFile::class.java) 36 } 37 38 override fun createUastHandler(context: JavaContext): UElementHandler { 39 return object : UElementHandler() { 40 override fun visitFile(node: UFile) { 41 node.imports.forEach { importStatement -> 42 visitImportStatement(context, importStatement) 43 } 44 } 45 } 46 } 47 48 private fun visitImportStatement( 49 context: JavaContext, 50 importStatement: UImportStatement, 51 ) { 52 val importText = importStatement.importReference?.asSourceString() ?: return 53 if (ILLEGAL_IMPORT == importText) { 54 context.report( 55 issue = ISSUE, 56 scope = importStatement, 57 location = context.getLocation(importStatement), 58 message = "collectAsState considered harmful", 59 ) 60 } 61 } 62 63 companion object { 64 @JvmField 65 val ISSUE = 66 Issue.create( 67 id = "OverlyEagerCollectAsState", 68 briefDescription = "collectAsState considered harmful", 69 explanation = 70 """ 71 go/sysui-compose#collect-as-state 72 73 Don't use collectAsState as it will set up a coroutine that keeps collecting from a 74 flow until its coroutine scope becomes inactive. This prevents the work from being 75 properly paused while the surrounding lifecycle becomes paused or stopped and is 76 therefore considered harmful. 77 78 Instead, use Flow.collectAsStateWithLifecycle(initial: T) or 79 StateFlow.collectAsStateWithLifecycle(). These APIs correctly pause the collection 80 coroutine while the lifecycle drops below the specified minActiveState (which 81 defaults to STARTED meaning that it will pause when the Compose-hosting window 82 becomes invisible). 83 """ 84 .trimIndent(), 85 category = Category.PERFORMANCE, 86 priority = 8, 87 severity = Severity.ERROR, 88 implementation = 89 Implementation( 90 CollectAsStateDetector::class.java, 91 Scope.JAVA_FILE_SCOPE, 92 ), 93 ) 94 95 private val ILLEGAL_IMPORT = "androidx.compose.runtime.collectAsState" 96 } 97 } 98