마스터Q&A 안드로이드는 안드로이드 개발자들의 질문과 답변을 위한 지식 커뮤니티 사이트입니다. 안드로이드펍에서 운영하고 있습니다. [사용법, 운영진]

뷰바인딩 베이스액티비티를 사용하는데

0 추천
A액티비티에서 BaseActivity를 상속받아서 사용하고 있습니다

BaseActivity에 바텀메뉴만들어서 BaseActivity에 있는 바텀메뉴 제어하는 메소드를 A액티비티에서 쓰고자

BaseActivity 뷰에 접근하고 하는데, 지금   Caused by: java.lang.IllegalStateException: all_view_text_1 must not be null이 나오는데.. 어떻게 해야하는지

현재 베이스액티비티 레이아웃이
프레임레이아웃
바텀메뉴바

이렇게 되어있어서 바텀메뉴클릭시 프레임레이아웃만 교체하는 형태고, 바텀메뉴는 해당 아이콘에 불이들어오는형태인데 뷰바인딩을 적용하려니 접근을 못하는거같아요.. 접근하는 방법이 없을까요?
수원통학러 (3,570 포인트) 님이 2021년 4월 17일 질문

2개의 답변

0 추천

안드로이드의 Activity는 이미 어마어마하게 큰 클래스이기 때문에, BaseActivity를 사용하는 것보다는 Composition 을 사용하는 것이 훨씬 더 좋은 방법입니다. 이게 일반적인 접근 방법이구요.

ViewBinding의 경우 BaseActivity에서 상속받을 Activity에서 어떤 클래스를 사용할지 모르므로, 바로 접근하시기는 힘들어 보이네요, 추상화해서 사용하실 수는 있습니다. 만약 BaseActivity 에 ViewBinding에 접근하는 코드를 넣으셨다면 별도의 클래스로 분리하시고 BaseActivity 를 상속받는 클래스에서 Inject(주입)해서 사용하세요.

abstract class ViewBindingActivity<VB extends ViewBinding> extends AppCompatActivity {
        
      protected ViewBinding binding;


       public void onCreate(Bundle savedInstance) {
               super.onCreate(savedInstance);
               binding = getViewBinding();
               setContentView(binding.root);
       }

       protected abstract VB getViewBinding()

}

public class MainActicity extends ViewBindingActivity<MainActivityBinding {

      @Override
      public void MainActivityBinding getViewBinding() {
               MainActivityBinding.inflate(...)
     }
}


또는 
public class ViewBindingFactory {
    private final LayoutInflator layoutInflator;

   public ViewBindingFactory(LayoutInflator layoutInflator) {
        this.layoutInflator = layoutInflator;
   }
    
    public  MainActivityBinding getActivityMain() {
         // MainActivity용  뷰바인딩 코드 작성
    }
}
spark (226,420 포인트) 님이 2021년 4월 18일 답변
spark님이 2021년 4월 18일 수정
0 추천

어떤 식으로 뷰를 Composition할 수 있는지 샘플코드를 보여드릴게요.

먼저 레이아웃 파일입니다.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingTop="?attr/actionBarSize">


    <FrameLayout
        android:id="@+id/fragmentFrame"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        app:layout_constraintBottom_toTopOf="@id/bottomNavigationFrame"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent" />

    <FrameLayout
        android:id="@+id/bottomNavigationFrame"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        />

</LinearLayout>

 

bottmNavigationFrame은 원래 BottomNavigationView가 있던 자리인데,BottomNavigationView을 집어넣어서 사용하거라 FameLayout으로 대체했습니다. FrameLayout에 프레그먼트를 add하는 것과 유사합니다. 

BottomNavigationView를 별도의 클래스로 이동시킬겁니다. 이걸 bottmNavigationFrame에 끼워 넣을 건데, Activity 와 BottomNavigationView 간의 커뮤니케이션은 Observer patter을 통해 할 겁니다.

BottomNavigationMenu용  레이아웃 파일입니다.

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">

    <item
        android:id="@+id/navigation_home"
        android:icon="@drawable/ic_home_black_24dp"
        android:title="@string/title_home" />

    <item
        android:id="@+id/navigation_dashboard"
        android:icon="@drawable/ic_dashboard_black_24dp"
        android:title="@string/title_dashboard" />

    <item
        android:id="@+id/navigation_notifications"
        android:icon="@drawable/ic_notifications_black_24dp"
        android:title="@string/title_notifications" />

</menu>

 

이제 핵심 클래스인 BottomNavigationViewMvc입니다. BottomNavigationViewMvc 클래스는 BottomNavigationView 의 wrapper입니다. 뷰계열의 클래스가 아닌 이유는 재사용성을 높이기 위해서입니다. 이렇게 하면 여러 액티비티들에서 재사용이 아주 쉬워집니다. 

// 옵저버 패턴을 사용하기 위한 인터페이스입니다. 옵저버 클래스에서 Observable을 구현한 클래스에 등록을 해줍니다.
public interface Observable<LISTENER> {
    void registerListener(LISTENER listener);
    void unregisterListener(LISTENER listener);
}

// 옵저버 등록과 해제를 처리하는 기본클래스 입니다.
public abstract class BaseObservable<LISTENER> implements Observable<LISTENER> {

    protected Set<LISTENER> listeners = new HashSet<>();


    @Override
    public void registerListener(LISTENER listener) {
        listeners.add(listener);
    }

    @Override
    public void unregisterListener(LISTENER listener) {
        listeners.remove(listener);
    }
}

public enum BottomMenuItem {
    HOME,
    DASHBOARD,
    NOTIFICATION
}

// BottomNaviationView는 BaseObservable을 구현하여 Observable이 됩니다.
// 이 클래스를 사용하는 클래스는 옵저버가 되고 등록을 해주면 됩니다.
public class BottomNavigationViewMvc extends BaseObservable<BottomNavigationViewMvc.Listener> implements OnNavigationItemSelectedListener {

    public interface Listener {
        void onBottomMenuClicked(BottomMenuItem menuItem);
    }

    private LayoutBottomNavigationBinding binding;

    public BottomNavigationViewMvc(LayoutInflater layoutInflater) {
        inflateLayout();
        initBottomNavigation(layoutInflater);
    }

    private void inflateLayout() {
        binding = LayoutBottomNavigationBinding.inflate(layoutInflater, null, false);
    }

    private void initBottomNavigation(LayoutInflater layoutInflater)) {
        binding.navView.setOnNavigationItemSelectedListener(this);
    }

    @Override
    public boolean onNavigationItemSelected(@NonNull MenuItem item) {
        BottomMenuItem bottomMenuItem;
        switch (item.getItemId()) {
            case R.id.navigation_home:
                    bottomMenuItem = BottomMenuItem.HOME;
                    break;
            case R.id.navigation_dashboard:
                    bottomMenuItem = BottomMenuItem.DASHBOARD;
                    break;
            case R.id.navigation_notifications:
                    bottomMenuItem = BottomMenuItem.NOTIFICATION;
                    break;
            default:
                throw new RuntimeException("Unexpected bottom menu: " + item.getItemId());

        }

       notifyBottomMenuClicked(bottomMenuItem);

        return true;
    }

    // 옵저버들에게 메뉴가 클릭되었음을 알려줍니다.
    private void notifyBottomMenuClicked(BottomMenuItem bottomMenuItem) {
          for (Listener listener : listeners) {
            listener.onBottomMenuClicked(bottomMenuItem);
        }
    }

    public View getRoot() {
        return binding.getRoot();
    }
}

 

액티비티에서는 BottomNavigationViewMvc의 rootView을 bottomNavigationFrame에 집어 넣어 줍니다.

// 옵저버로 등록할 수 있도록 BottomNavigationViewMvc.Listener를 구현해 줍니다.
public class MainActivity extends AppCompatActivity implements BottomNavigationViewMvc.Listener {

    private ActivityMainBinding binding;

    private BottomNavigationViewMvc bottomNavigationViewMvc;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setupBottomMenu();
        setContentView(binding.getRoot());
    }

    private void setupBottomMenu() {
        bottomNavigationViewMvc = new BottomNavigationViewMvc(getLayoutInflater());
        // bottomNavigationFrame에 BottomNaviagtionView를 추가합니다.
        binding.bottomNavigationFrame.addView(bottomNavigationViewMvc.getRoot());
    }

    // onStart와 onStop에서 옵저버로 등록을 하고 해제하는 부분은 Memory leak 을 방지하가 위해 아주 중요합니다.
    @Override
    protected void onStart() {
        super.onStart();
        bottomNavigationViewMvc.registerListener(this);
    }

    @Override
    protected void onStop() {
        super.onStop();
        bottomNavigationViewMvc.unregisterListener(this);
    }

    @Override
    public void onBottomMenuClicked(BottomMenuItem menuItem) {
        switch (menuItem) {
            case HOME:
                System.out.println("HOME clicked");
                break;
            case DASHBOARD:
                System.out.println("DASHBOARD clicked");
                break;
            case NOTIFICATION:
                System.out.println("NOTIFICATION clicked");
                break;
        }
    }
}

BottomNavigationViewMvc는 다른 액티비티나 프레그먼트 들에서도 옵저버를 등록/해제 함으로써 사용할 수 있습니다. 그리고 ViewBinding을 사용하는지 findViewById를 사용하는지는 BottoomNavigationViewMvc 안에 감춰져 있습니다. BottoomNavigationView 관련 수정사항이 생긴다면 BottomNavigationViewMvc 클래스만 고치면 되겠죠.

spark (226,420 포인트) 님이 2021년 4월 18일 답변
spark님이 2021년 4월 18일 수정
...