AndroidX MenuHost

AndroidX MenuHost

Sep 6, 2021. | By: pluulove

본 글은 Jetpack AndroidX Core/Activity에 추가된 MenuHost를 다루는 글입니다.

최근 AndroidX ComponentActivity에 MenuHost 인터페이스를 구현하도록 새롭게 추가되었습니다. 이를 통해 다른 Component에서 MenuProvider 인스턴스를 Activity에 추가함으로 Menu를 노출할 수 있게 변경되었습니다.를 활동에 추가하여 ActionBar에 메뉴 항목을 추가할 수 있습니다. 또한, Lifecycle가 소멸될 때 자동으로 제거되는 기능도 추가되었습니다.

각 MenuProvider는 수명 주기 상태에 따라 해당 메뉴 항목의 가시성을 자동으로 제어하고 수명 주기가 소멸될 때 MenuProvider 제거를 처리하는 수명 주기와 함께 선택적으로 추가될 수 있습니다.


먼저 살펴볼 AndroidX 라이브러리 버전은 아래와 같습니다.

Release

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

Code

먼저 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의 업데이트된 코드를 통해서 알게 된 사실은 아래와 같습니다.

  • MenuHost 인터페이스를 ComponentActivity가 구현하고 있다
  • 메뉴에 대한 대부분의 기능은 MenuHostHelper에 이관하여 처리한다
  • MenuProvider를 통해서 메뉴를 등록/제거 처리가 된다

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

실제 사용

Activity에서 사용

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

Fragment에서 사용

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

Summary

이번 업데이트로 복수 Fragment 및 Compose 사용 시 Menu 핸들링이 더 편해질 것 같습니다. 추가로 ComponentActivity에 들어간 Result API 관련 스펙도 결과적으로 몇몇 기존 API가 Deprecated 되었다. 왠지 이 API 추가로 기존 Menu 관련 메소드도 Deprecated 되는게 아닐까? 하는 상상도 해봅니다.

comments powered by Disqus

Currnte Pages Tags

Android AndroidX

About

Pluu, Android Developer Blog Site

이전 블로그 링크 :네이버 블로그

Using Theme : SOLID SOLID Github

Social Links