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

Rxjava 백그라운드에서 데이터 받아와서 RecycleView에 넣는 법?

0 추천
package kr.sy.android.fastfood.ui.home;

import android.app.Activity;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import com.google.android.material.tabs.TabLayout;

import java.util.List;

import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
import kr.sy.android.fastfood.DBService;
import kr.sy.android.fastfood.R;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
import retrofit2.converter.gson.GsonConverterFactory;

public class HomeFragment extends Fragment {

    private HomeViewModel homeViewModel;
    private FragmentActivity myContext;
    static List<TabListViewModel> loadedlist = null; //라이브 데이터에 넣어야함

    @Override
    public void onAttach(Activity activity) {
        myContext=(FragmentActivity) activity;
        super.onAttach(activity);
    }

    public View onCreateView(@NonNull LayoutInflater inflater,
                             ViewGroup container, Bundle savedInstanceState) {
        homeViewModel =
                new ViewModelProvider(this).get(HomeViewModel.class);
        //뷰 생성
        View root = inflater.inflate(R.layout.fragment_home, container, false);

        RecyclerView recyclerView = (RecyclerView)root.findViewById(R.id.recyclerView);
        LinearLayoutManager layoutManager = new LinearLayoutManager(myContext, LinearLayoutManager.VERTICAL, false);
        recyclerView.setLayoutManager(layoutManager);

        updateList(1,recyclerView);

        // TabLayout 뷰를 가져온다.
        TabLayout tabs = (TabLayout) root.findViewById(R.id.tabLayout);
        // 탭 선택이 변경될 때 호출되는 탭 선택 수신기입니다.
        tabs.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
            @Override
            public void onTabSelected(TabLayout.Tab tab) {

                int position = tab.getPosition();
                
                if(position == 0){
                    updateList(1,recyclerView);
                }else if (position == 1){
                    updateList(2,recyclerView);
                }else if (position == 2){
                    updateList(3,recyclerView);
                }else if (position == 3){
                    updateList(4,recyclerView);
                }else if (position == 4){
                    updateList(5,recyclerView);
                }else if (position == 5){
                    updateList(6,recyclerView);
                }

            }

            @Override
            public void onTabUnselected(TabLayout.Tab tab) {

            }

            @Override
            public void onTabReselected(TabLayout.Tab tab) {

            }
        });

        return root;
    }

    public Single<List<TabListViewModel>> fetchCompanyinfo(int category_index){
        Retrofit retrofit = new Retrofit.Builder().addCallAdapterFactory(RxJava2CallAdapterFactory.create()).addConverterFactory(GsonConverterFactory.create()).baseUrl("http://13.58.187.197:8080").build();

        DBService service = retrofit.create(DBService.class);

        return service.getCompanyinfo(category_index).subscribeOn(Schedulers.io());
    }

    private void updateList(int category_index,RecyclerView recyclerView){
        fetchCompanyinfo(category_index).observeOn(AndroidSchedulers.mainThread())
                .subscribe(
                        v -> loadedlist = v.subList(0,v.size()) ,
                        err -> System.err.println("onError() : err :" + err.getMessage()));

    }

    private void updateUI(RecyclerView recyclerView){
        if(loadedlist != null) {
            CustomerAdapter adapter = new CustomerAdapter(myContext);
            adapter.addItem(loadedlist);
            recyclerView.setAdapter(adapter);
        }
    }

}

subscribe ( v -> )

v에서 받아온 데이터를 가공해서 리싸이클뷰에 넣고 싶은데 어떤식으로 넣어야 할까요?

초보입니당 (300 포인트) 님이 2021년 4월 12일 질문

2개의 답변

0 추천
 
채택된 답변

코드는 거의 다 작성을 하신 것 같네요.

몇 가지 눈에 띄는 수정사항을 알려드릴게요. 가능하면 말씀드리는 방향을 생각해 보시고 적용하시는게 나중에 변경사항이 생길 때 많은 도움이 될겁니다.

Retrofit을 생성하는 부분은 거의 모든 경우는 앱에 걸쳐서 한개의 인스턴스(Singleton)만 필요합니다. 네트웍작업을 리소스를 많이 소모하는 작업이므로 API를 호출할 때마다 생성한다면 낭비가 됩니다. 그리고 데이터를 공유하거나 하지 않기 때문에 따라서 해당 부분을 Singleton으로 생성하도록 수정해 보세요.

Retrofit retrofit = new Retrofit.Builder()
  .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
  .addConverterFactory(GsonConverterFactory.create())
  .baseUrl("http://13.58.187.197:8080").build();

 

Retrofit과 마찬가지로 DBService 는 fetchCompanyinfo가 호출될 때마다 다시 생성할 필요가 없기 때문에, 외부에서 한번만 생성하도록 하세요.

DBService service = retrofit.create(DBService.class);

 

fetchCompanyinfo가 왜 ViewModel을 리턴하나요? 님이 리사이클러뷰 어댑터에 사용하실
데이터 타입이 와야할 것 같은데요. 만약 TabListViewModel가 그 클래스라면, 적합한 이름으로 꼭 바꾸세요.
안드로이드의 ViewModel을 연상시켜서, 코드를 상당히 읽기 헷갈리게 합니다.
이름을 님의 의도가 잘 드러나게 잘 짓는 것은 제일 어렵고도 첫번째로 많이 강조하는 좋은 코드의 필수조건입니다.

public class CompanyInfo {
     private final int categoryIndex;
     private final List<Company> companies;
    //constructor, getter setter생략
}

public Single<CompanyInfo> fetchCompanyinfo(int categoryIndex){
     return service.getCompanyinfo(categoryIndex)
                           .subscribeOn(Schedulers.io());
}


DBService 서비스를 호출하는 부분은 데이터를 처리하는 부분이므로 뷰인 Activity에 있을 필요가 없습니다.
Activity는 ViewModel에서 던져주는 데이터를 가지고 화면에 표시하기만 하면 됩니다. 따라서 아래코드는 ViewModel 을 통해 호출되어야 합니다. 실제 데이터의 호출은 UseCase가 백그라운드 쓰레드 안에 합니다. DbService는 UseCase에 필요한 의존성(Depedency)가 되고 외부에서 생성해서 넣어줍니다. 마찬가지로  ViewModel에도 외부에서 UseCase를 넣어줍니다.

public class SchedulerProvider {
     
    public void io(): Schduer {
        return Schedulers.io();
    }
}

public FecthCompanyUseCase {
     private final DbService dbService;

     public FecthCompanyUseCase(DbService dbService, SchedulerProvider schedulers) {
          this.dbService = dbService;
     }
   
     public fetchCompanyInfo(int categoryIndex): Single<CompanyInfo> {
        // observeOn이 필요없는 이유는 LiveData.postValue는 백그라운드 쓰레드에서도 값을 전달할 수 있기 때문입니다.
        // liveData.value 를 사용하신다면 메인쓰레드로 전환해야 겠죠.
        return dbService.getCompanyinfo(categoryIndex)
                                  .subscribeOn(schedulers.io());
     }


}


public class HomeViewModel extends ViewModel {
    private final FecthCompanyUseCase fetchCompanyUseCase;
    private final MutableLiveData<CompanyInfo> companyInfoLiveData;


    private Disposble disposable = null;
   

    // onClear에서 RxJava에서 사용했던 리소스를 해제해주셔 합니다. 그래야 메모리 누수가 없어요.

    @Override
    public void onCleared() {
       super.onCleared();
       if( diposable != null )
            disposable.dispose();
    }
    

    public LiveData<CompanyInfo> getCompanyInfoLiveData() {
         return this.companyInfosData;
    }

    public HomeViewModel(FecthCompanyUseCase fetchCompanyUseCase) {
          this.fetchCompanyUseCase = fetchCompanyUseCase
    }

 // observeOn이 필요없는 이유는 LiveData.postValue는 백그라운드 쓰레드에서도 값을 전달할 수 있기 때문입니다.
        // liveData.value 를 사용하신다면 메인쓰레드로 전환해야 겠죠.
     public void fetchCompanyinfo(int categoryIndex){
            diposable = fetchCompanyUseCase
                          .fetchCompanyInfo(categoryIndex)
                           .subscribe(
                                        v -> notifyCompanyInfos(v) ,
                                         err -> System.err.println("onError() : err :" + err.getMessage()
                                 
     }


    private void notifyCompanyInfo(companyInfos: List<CompanyInfo>) {
            companyInfoLiveData.postValue(companyInfos);
    }

     

     
}

 

이제 View쪽에서 ViewModel에 있는  companyInfosLiveData를 observe하고 있다가, 값이 오면 그 때 리사이클러뷰를 갱신해주면 됩니다.

 

public class HomeFragment extends Fragment {

    //  중복코드 생략
 
    public View onCreateView(@NonNull LayoutInflater inflater,
                             ViewGroup container, Bundle savedInstanceState) {
        homeViewModel = ... // 이 부분을 기존 코드대로 사용할 수 없을 겁니다. ViewModelFactory를 통해 필요한 ViewModel에 필요한 의존 클래스들을 주입해주어야 합니다.
        // 이부분은 출근관계로 시간이 없어서 생략합니다. 안드로이드 개발 문서에 샘플코드가 있으니 거기서 가져다 쓰세요.
        //뷰 생성
       
       
        // ViewModel 에서 변경된 데이터가 있는지 observing
        final Observer<CompanyInfo> companyInfoObserver = new Observer<>() {
            @Override
            public void onChanged(@Nullable final CompanyInfo companyInfo) {
                 updateComanyInfo(companyInfos;
            }
        };

        // Observe the LiveData, passing in this activity as the LifecycleOwner and the observer.
        model.getCompanyInfoLiveData().companyInfoObserver(this.getViewLifecycleObserver(), companyInfObserver);
    }


    private void updateComanyInfo(CompanyInfo companyInfo){
         //여기서 리사이클러뷰 갱신
 
    }
 }

 

spark (226,420 포인트) 님이 2021년 4월 13일 답변
초보입니당님이 2021년 4월 13일 채택됨
매번 답변해주셔서 정말 많은 도움 되고있습니다.
감사합니다.
0 추천

Scheduler를 사용하세요. 

API  호출같은 백그라운드 작업을 할 때는 Single.subscribeOn에 background thread 를 설정하고,

리사이크러뷰를 갱신해주는 것과 같이 메인쓰레드 작업을 해야 할 경우에는  observeOn에 Main thread 를 설정하세요.

 

single.subscribeOn(Schedulers.io())

       .observeOn(AndroidSchedulers.mainThread())

       .subscribe(// 성공일 때 RecyclerView 갱신)

 

좀 더 자세한 내용을 RxJava API 문서를 참조하세요.

spark (226,420 포인트) 님이 2021년 4월 12일 답변
fetchCompanyinfo(category_index).observeOn(AndroidSchedulers.mainThread())
                .subscribe(
                        v -> loadedlist = v.subList(0,v.size()) ,
                        err -> System.err.println("onError() : err :" + err.getMessage()));
 
   여기서 갱신을 어떻게 해야할까요?
...