Compose with android view

y— title: Compose with Android View date: 2023-07-11T20:30:00+09:00 author: Seungmin Yang layout: post tag: [Android, Compose] —

Compose with Android View

Compose는 앱에서 compose로 migration을 진행 중 기존 Android View System과 상호 운용할 수 있도록 API를 제공한다.

ComposeView

Activity에서 Compose를 기반으로 하는 view를 만드려면 ComponentActivitysetContent 메소드를 호출하여 Composable을 전달한다.

class ExampleActivity : ComponentActivity() {  
    override fun onCreate(savedInstanceState: Bundle?) {  
        super.onCreate(savedInstanceState)  
  
        setContent { // In here, we can call composables!  
            MaterialTheme {  
                Greeting(name = "compose")  
            }  
        }    }  
}  
  
@Composable  
fun Greeting(name: String) {  
    Text(text = "Hello $name!")  
}

ComponentActivity 더 높은 수준의 기능(Component)을 구현한 액티비티 클래스(여기서 더 높은 수준의 기능은 Activity Result API, Jetpack Compose등 AndroidX에서 제공하는 여러 기능들)

기존 Android View 시스템에서는 AppCompatActivity를 상속하여 사용했는데, AppCompatActivity는 아래의 상속 구조를 따른다.

Activity -> ComponentActivity -> FragmentActivity -> AppCompatActivity

FragmentActivity에서 Activity에 Fragment를 추가하기 위한 API를 포함하고 있으며 AppCompatActivity는 구버전 안드로이드와의 호환성을 보장해주는 Activity이다.

Compose를 기반으로 하는 액티비티는 ComponentActivity를 상속하여 구현하게 되는데, 이는 Compose는 Fragment라는 개념을 사용하지 않으며 특정 안드로이드 버전에 의존하는 기능(대표적으로 ActionBar와 Toolbar)와 같은 기능을 사용하지 않음을 방증할 수 있다.

setContent는 확장 함수로 구현되어 있으며 ComposeView를 통해 뷰를 생성 후 setContentView를 통해 화면에 보여줄 수 있도록 하는 기능을 포함하고 있다.

Compose in fragment

프래그먼트에서 Compose를 사용하려면 동일하게 ComposeView를 사용하고 setContent를 호출한다.

<androidx.compose.ui.platform.ComposeView  
	android:id="@+id/compose_view"  
	android:layout_width="match_parent"  
	android:layout_height="match_parent" />
override fun onCreateView(  
    inflater: LayoutInflater,  
    container: ViewGroup?,  
    savedInstanceState: Bundle?  
): View {  
    _binding = FragmentExampleBinding.inflate(inflater, container, false)  
    val view = binding.root  
    binding.composeView.apply {  
        // Dispose of the Composition when the view's LifecycleOwner  
        // is destroyed        setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)  
        setContent {  
            // In Compose world  
            MaterialTheme {  
                Text("Hello Compose!")  
            }  
        }    
    }
    
    return view  
}

ComposeView를 return하여 xml을 사용하지 않고 프래그먼트를 사용할 수 있다.

class ExampleFragmentNoXml : Fragment() {  
  
    override fun onCreateView(  
        inflater: LayoutInflater,  
        container: ViewGroup?,  
        savedInstanceState: Bundle?  
    ): View {  
        return ComposeView(requireContext()).apply {  
            // Dispose of the Composition when the view's LifecycleOwner  
            // is destroyed            setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)  
            setContent {  
                MaterialTheme {  
                    // In Compose world  
                    Text("Hello Compose!")  
                }  
            }        
        }    
    }  
}

동일한 레아아웃에 여러 ComposeView가 있으면 savedInstanceState가 작동하기 위해서는 각 요소에 고유한 ID가 있어야 한다.

addView(  
	ComposeView(requireContext()).apply {  
	    setViewCompositionStrategy(  
	        ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed  
	    )  
	    id = R.id.compose_view_x  
	    // ...  
	}  
)  
addView(TextView(requireContext()))  
addView(  
	ComposeView(requireContext()).apply {  
	    setViewCompositionStrategy(  
	        ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed  
	    )  
	    id = R.id.compose_view_y  
	    // ...  
	}  
)

ViewCompositionStrategy

기본적으로 Compose는 뷰가 window에서 분리되면 Composition을 삭제한다(DisposeOnDetachedFromWindowOrReleasedFromPool). 이런 구조가 바람직하지 않을 수 있으며 이 경우에는 Custom strategy를 만들 수 있다.

View in Compose

@Composable  
fun CustomView() {  
    var selectedItem by remember { mutableStateOf(0) }  
  
    // Adds view to Compose  
    AndroidView(  
        modifier = Modifier.fillMaxSize(), // Occupy the max size in the Compose UI tree  
        factory = { context ->  
            // Creates view  
            MyView(context).apply {  
                // Sets up listeners for View -> Compose communication  
                setOnClickListener {  
                    selectedItem = 1  
                }  
            }        },  
        update = { view ->  
            // View's been inflated or state read in this block has been updated  
            // Add logic here if necessary  
            // As selectedItem is read here, AndroidView will recompose            // whenever the state changes            // Example of Compose -> View communication            view.selectedItem = selectedItem  
        }  
    )  
}  
  
@Composable  
fun ContentExample() {  
    Column(Modifier.fillMaxSize()) {  
        Text("Look at this CustomView!")  
        CustomView()  
    }  
}

Compose에서 Android View를 사용하려면 AndroidView Composable을 사용한다. 이 방법은 NaverMap과 같은 아직 사용할 수 없는 경우 유용하다.

View를 return하는 람다를 전달하며 뷰가 확장될 때 호출되는 update콜백을 제공한다. AndroidView는 update 콜백 내의 state가 변경될 때마다 recomposition이 일어난다.

androidx.compose.ui:ui-viewbinding 라이브러리의 AndroidViewBinding을 사용하여 뷰 바인딩을 사용할 수 있다. 또한 이를 이용하여 Fragment를 추가할 수 있다.

Android Framework in Compose

Compose는 Activity, Fragment와 같은 Android View 클래스에서 호출되며, Context, 리소스, Service, BR같은 안드로이드의 프레임워크 클래스를 호출할 수도 있다.

CompositionLocal은 Android Framework의 인스턴스를 전달하는 데에도 사용할 수 있다. LocalContext, LocalConfiguration, LocalView와 같은 방법을 제공한다.

@Composable  
fun ToastGreetingButton(greeting: String) {  
    val context = LocalContext.current  
    Button(onClick = {  
        Toast.makeText(context, greeting, Toast.LENGTH_SHORT).show()  
    }) {  
        Text("Greet")  
    }  
}

© 2021. All rights reserved.

Powered by Hydejack v9.1.6