본 글은 Jetpack AndroidX Core/Activity에 추가된 MenuHost를 다루는 글입니다.
최근 AndroidX ComponentActivity에 MenuHost
인터페이스를 구현하도록 새롭게 추가되었습니다. 이를 통해 다른 Component에서 MenuProvider 인스턴스를 Activity에 추가함으로 Menu를 노출할 수 있게 변경되었습니다.를 활동에 추가하여 ActionBar에 메뉴 항목을 추가할 수 있습니다. 또한, Lifecycle가 소멸될 때 자동으로 제거되는 기능도 추가되었습니다.
각 MenuProvider는 수명 주기 상태에 따라 해당 메뉴 항목의 가시성을 자동으로 제어하고 수명 주기가 소멸될 때 MenuProvider 제거를 처리하는 수명 주기와 함께 선택적으로 추가될 수 있습니다.
먼저 살펴볼 AndroidX 라이브러리 버전은 아래와 같습니다.
Core and Core-ktx Version 1.7.0
https://developer.android.com/jetpack/androidx/releases/core#1.7.0-alpha02
Activity Version 1.4.0-alpha01
https://developer.android.com/jetpack/androidx/releases/activity#1.4.0-alpha01
먼저 AndroidX Activity의 ComponentActivity 클래스에 MenuHost
를 구현하도록 정의된 부분이 주된 변경 포인트입니다. 해당 인터페이스가 적용된 코드를 살펴보겠습니다.
package androidx.activity;
public class ComponentActivity extends androidx.core.app.ComponentActivity implements
...,
MenuHost {
// ...
// MenuHost를 구현하기 위한 Helper 클래스가 내부에 선언되어 있습니다.
private final MenuHostHelper mMenuHostHelper = new MenuHostHelper(this::invalidateMenu);
// 아래 override된 함수는 MenuHost에서 선언된 함수를 구현한 부분입니다.
// 대부분의 구현이 MenuHostHelper로 기능을 이관한 함수를 호출하는 형태로 되어 있습니다.
@Override
public boolean onCreateOptionsMenu(@NonNull Menu menu) {
super.onCreateOptionsMenu(menu);
mMenuHostHelper.onCreateMenu(menu, getMenuInflater());
return true;
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if (super.onOptionsItemSelected(item)) {
return true;
}
return mMenuHostHelper.onMenuItemSelected(item);
}
@Override
public void addMenuProvider(@NonNull MenuProvider provider) {
mMenuHostHelper.addMenuProvider(provider);
}
@Override
public void addMenuProvider(@NonNull MenuProvider provider, @NonNull LifecycleOwner owner) {
mMenuHostHelper.addMenuProvider(provider, owner);
}
@Override
@SuppressLint("LambdaLast")
public void addMenuProvider(@NonNull MenuProvider provider, @NonNull LifecycleOwner owner,
@NonNull Lifecycle.State state) {
mMenuHostHelper.addMenuProvider(provider, owner, state);
}
@Override
public void removeMenuProvider(@NonNull MenuProvider provider) {
mMenuHostHelper.removeMenuProvider(provider);
}
@Override
public void invalidateMenu() {
invalidateOptionsMenu();
}
}
https://android-review.googlesource.com/c/platform/frameworks/support/+/1806376
ComponentActivity의 업데이트된 코드를 통해서 알게 된 사실은 아래와 같습니다.
ComponentActivity가 구현하는 MenuHost
는 앱에 MenuItem을 MenuProvider를 호스팅하며 제어할 수 있는 인터페이스입니다.
public interface MenuHost {
// 전달된 MenuProvider를 MenuHost에 추가한다
// 필요한 경우 MenuProvider를 수동으로 제거해야 한다
void addMenuProvider(@NonNull MenuProvider provider);
// MenuProvider를 MenuHost에 추가하며,
// MenuProvider는 전달된 LifecycleOwner가 Lifecycle.Event.ON_DESTROY 이벤트를 수신하면 제거된다.
void addMenuProvider(@NonNull MenuProvider provider, @NonNull LifecycleOwner owner);
// MenuProvider를 MenuHost에 추가한다.
// LifecycleOwner가 전달된 Lifecycle.State에 도달하면 MenuProvider를 추가하며,
// Lifecycle.State보다 down 되면 제거한다.
@SuppressLint("LambdaLast")
void addMenuProvider(@NonNull MenuProvider provider, @NonNull LifecycleOwner owner,
@NonNull Lifecycle.State state);
// MenuProvider를 MenuHost에서 제거한다.
void removeMenuProvider(@NonNull MenuProvider provider);
// Menu 정보를 갱신한다.
void invalidateMenu();
}
https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:core/core/src/main/java/androidx/core/view/MenuHost.java
MenuProvider는 Component가 MenuItems를 제공 가능함을 나타내는 인터페이스입니다.
public interface MenuProvider {
// MenuProvider가 MenuItem을 메뉴로 확장하기 위해 MenuHost에 의해 호출된다.
void onCreateMenu(@NonNull Menu menu, @NonNull MenuInflater menuInflater);
// 메뉴에서 MenuItem이 선택되면 MenuHost에 의해 호출된다.
boolean onMenuItemSelected(@NonNull MenuItem menuItem);
}
https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:core/core/src/main/java/androidx/core/view/MenuProvider.java
MenuHost를 구현하기 위한 도우미 클래스입니다.
public class MenuHostHelper {
private final Runnable mOnInvalidateMenuCallback;
// 복수의 MenuProvider를 사용해 메뉴 노출
private final CopyOnWriteArrayList<MenuProvider> mMenuProviders = new CopyOnWriteArrayList<>();
// MenuHostHelper 초기화 시 InvalidateMenu Callback을 주입
public MenuHostHelper(@NonNull Runnable onInvalidateMenuCallback) {
mOnInvalidateMenuCallback = onInvalidateMenuCallback;
}
// 등록된 MenuProvider를 통해 menu를 구성
public void onCreateMenu(@NonNull Menu menu, @NonNull MenuInflater menuInflater) {
for (MenuProvider menuProvider : mMenuProviders) {
menuProvider.onCreateMenu(menu, menuInflater);
}
}
// 메뉴 선택시, MenuProvider에 처리되는지 체크
public boolean onMenuItemSelected(@NonNull MenuItem item) {
for (MenuProvider menuProvider : mMenuProviders) {
if (menuProvider.onMenuItemSelected(item)) {
return true;
}
}
return false;
}
// 전달된 MenuProvider를 MenuHost에 추가
// InvalidateMenu로 갱신 처리
public void addMenuProvider(@NonNull MenuProvider provider) {
mMenuProviders.add(provider);
mOnInvalidateMenuCallback.run();
}
// MenuProvider를 MenuHost에 추가하며,
// MenuProvider는 전달된 LifecycleOwner가 Lifecycle.Event.ON_DESTROY 이벤트를 수신하면 제거된다.
public void addMenuProvider(@NonNull MenuProvider provider, @NonNull LifecycleOwner owner) {
addMenuProvider(provider);
Lifecycle lifecycle = owner.getLifecycle();
lifecycle.addObserver((LifecycleEventObserver) (source, event) -> {
if (event == Lifecycle.Event.ON_DESTROY) {
removeMenuProvider(provider);
}
});
}
// MenuProvider를 MenuHost에 추가한다.
// LifecycleOwner가 전달된 Lifecycle.State에 도달하면 MenuProvider를 추가하며,
// Lifecycle.State보다 down 되면 제거한다.
@SuppressLint("LambdaLast")
public void addMenuProvider(@NonNull MenuProvider provider, @NonNull LifecycleOwner owner,
@NonNull Lifecycle.State state) {
Lifecycle lifecycle = owner.getLifecycle();
lifecycle.addObserver((LifecycleEventObserver) (source, event) -> {
if (event == Lifecycle.Event.upTo(state)) {
addMenuProvider(provider);
} else if (event == Lifecycle.Event.ON_DESTROY) {
removeMenuProvider(provider);
} else if (event == Lifecycle.Event.downFrom(state)) {
removeMenuProvider(provider);
}
});
}
// MenuProvider를 MenuHost에서 제거
public void removeMenuProvider(@NonNull MenuProvider provider) {
mMenuProviders.remove(provider);
mOnInvalidateMenuCallback.run();
}
}
https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:core/core/src/main/java/androidx/core/view/MenuHostHelper.java
class ExampleActivity : ComponentActivity(R.layout.activity_example) {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// "onCreateContextMenu"를 오버라이딩하지 않고 메뉴 추가
addMenuProvider(object : MenuProvider {
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
// 메뉴 inflate
menuInflater.inflate(R.menu.example_menu, menu)
}
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
// 메뉴 선택시 핸들링
return true
}
})
}
}
코드 출처 : https://developer.android.com/jetpack/androidx/releases/activity?hl=en#1.4.0-alpha01
class ExampleFragment : Fragment(R.layout.fragment_example) {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
// Activity에 정의된 MenuHost 인터페이스를 참조
val menuHost: MenuHost = requireActivity()
// "onCreateContextMenu"를 오버라이딩하지 않고 메뉴 추가
// MenuProvider를 viewLifecycleOwner과 Lifecycle.State.RESUMED을 사용해 메뉴가 표시될 시기를 정의
menuHost.addMenuProvider(object : MenuProvider {
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
// 메뉴 inflate
menuInflater.inflate(R.menu.example_menu, menu)
}
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
// 메뉴 선택시 핸들링
return true
}
}, viewLifecycleOwner, Lifecycle.State.RESUMED)
}
}
코드 출처 : https://developer.android.com/jetpack/androidx/releases/activity?hl=en#1.4.0-alpha01
이번 업데이트로 복수 Fragment 및 Compose 사용 시 Menu 핸들링이 더 편해질 것 같습니다. 추가로 ComponentActivity에 들어간 Result API
관련 스펙도 결과적으로 몇몇 기존 API가 Deprecated 되었다. 왠지 이 API 추가로 기존 Menu
관련 메소드도 Deprecated 되는게 아닐까? 하는 상상도 해봅니다.
comments powered by Disqus
Subscribe to this blog via RSS.
LazyColumn/Row에서 동일한 Key를 사용하면 크래시가 발생하는 이유
Posted on 30 Nov 2024