728x90

MVVM 구조의 핵심 사항만 소스코드로 예시한다.

전체 소스코드는 Github 에서 받아서 Project 에서 New Module을 생성하고 src 폴더를 덮어쓰기 해도 되고, 참고하면서 소스코드를 작성하는 것도 방법이다.

암호화함수를 모아둔 Class 는 오픈하지 않았으므로 전체적인 로직만 참고하면 도움될 것이다.

 

AddressRepository.java

import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
 
import com.link2me.android.common.CipherUtils;
import com.link2me.android.rview_ex3.model.AddressResult;
import com.link2me.android.rview_ex3.network.RetrofitAdapter;
import com.link2me.android.rview_ex3.network.RetrofitService;
import com.link2me.android.rview_ex3.network.RetrofitURL;
 
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
 
public class AddressRepository {
    private RetrofitService service;
    private MutableLiveData<AddressResult> mutableLiveData;
 
    public AddressRepository() {
        mutableLiveData = new MutableLiveData<>();
        service = RetrofitAdapter.getClient(RetrofitURL.BaseUrl).create(RetrofitService.class);
    }
 
    public void addressDataRepo(String search) {
        String keyword = CipherUtils.encryptAES(CipherUtils.URLkey());
        service.GetAddrData(keyword, search).enqueue(new Callback<AddressResult>() {
            @Override
            public void onResponse(Call<AddressResult> call, Response<AddressResult> response) {
                mutableLiveData.postValue(response.body());
            }
 
            @Override
            public void onFailure(Call<AddressResult> call, Throwable t) {
                mutableLiveData.postValue(null);
            }
        });
    }
 
    public LiveData<AddressResult> getAddressResultLiveData(){
        return mutableLiveData;
    }
}
 

 

 

AddressViewModel.java

public class AddressViewModel extends ViewModel {
    private AddressRepository repository;
    private LiveData<AddressResult> _liveData;
 
    public AddressViewModel() {
    }
 
    public void getAllAddressData(String search){
        repository.addressDataRepo(search);
    }
 
    public LiveData<AddressResult> get_liveData(){
        if(_liveData == null){
            repository = new AddressRepository();
            _liveData = repository.getAddressResultLiveData();
        }
        return _liveData;
    }
}
 

 

 

AddressListAdapter.java

 
/***
 * https://www.youtube.com/watch?v=xPPMygGxiEo 동영상 시청하면 개념 이해하는데 엄청 도움됨
 */
 
// RecyclerView.Adapter 대신에 ListAdapter<T, VH> 를 상속한다.
// T는 리사이클러뷰가 화면에 표시할 데이터의 타입이다.
public class AddressListAdapter extends ListAdapter<Address_Item, AddressListAdapter.ViewHolder> {
    private final String TAG = this.getClass().getSimpleName();
    Context context;
 
    // 인터페이스 선언 -------------------------------------------------------------
    private OnItemClickListener mListener;
 
    public interface OnItemClickListener {
        void onItemClicked(Address_Item item, int position);
    }
 
    public void setOnItemClickListener(OnItemClickListener listener){
        mListener = listener;
    }
    // 인터페이스 ----------------------------------------------------------------
 
    public AddressListAdapter(Context context, @NonNull DiffUtil.ItemCallback<Address_Item> diffCallback) {
        super(diffCallback);
        this.context = context;
    }
 
    public void selectAll(int list_count){ // checkbox 전체 선택
        for(int i=0;i < list_count;i++){
            getItem(i).setCheckBoxState(true);
        }
        notifyDataSetChanged();
    }
    public void unselectall(int list_count){ // checkbox 전체 해제
        for(int i=0;i < list_count;i++){
            getItem(i).setCheckBoxState(false);
        }
        notifyDataSetChanged();
    }
 
    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        ItemAddressBinding binding = ItemAddressBinding.inflate(LayoutInflater.from(parent.getContext()),parent,false);
        return new ViewHolder(binding);
    }
 
    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        // 실제로 데이터를 표시하는 부분
        //Address_Item currentItem = rvItemList.get(position);
        Address_Item currentItem = getItem(position);
        holder.bindItem(currentItem, position);
    }
 
    public class ViewHolder extends RecyclerView.ViewHolder{
        ItemAddressBinding itemBinding;
 
        public ViewHolder(@NonNull ItemAddressBinding binding) {
            super(binding.getRoot());
            itemBinding = binding;
        }
 
        void bindItem(Address_Item item, int position){
            String photoURL = RetrofitURL.BaseUrl + "photos/" + item.getPhoto();
            if(photoURL.contains("null")){
                Glide.with(context).asBitmap().load(R.drawable.photo_base).into(itemBinding.profileImage);
            } else {
                Glide.with(context).asBitmap().load(photoURL).into(itemBinding.profileImage);
            }
            itemBinding.childName.setText(item.getUserNM());
            itemBinding.childMobileNO.setText(PhoneNumberUtils.formatNumber(item.getMobileNO()));
            itemBinding.childOfficeNO.setText(PhoneNumberUtils.formatNumber(item.getTelNO()));
 
            itemBinding.profileImage.setOnClickListener(view1 -> {
                if(mListener != null){
                    mListener.onItemClicked(item,position); // 클릭의 결과로 값을 전달
                }
            });
 
            if (isCheckFlag == false) {
                itemBinding.callBtn.setVisibility(View.VISIBLE);
                itemBinding.callBtn.setOnClickListener(view -> builder.show());
                itemBinding.listcellCheckbox.setVisibility(View.GONE);
                itemBinding.childLayout.setOnClickListener(view ->
                        Toast.makeText(context, "상세보기를 눌렀습니다 ===" + position + " , idx :" + item.getIdx(), Toast.LENGTH_SHORT).show());
 
                itemBinding.childLayout.setOnLongClickListener(v -> {
                    isCheckFlag = true;
                    constraintLayout.setVisibility(View.VISIBLE);
                    notifyDataSetChanged();
                    return true;
                });
            } else {
                itemBinding.callBtn.setVisibility(View.GONE);
                //convertView.setClickable(false);
                itemBinding.listcellCheckbox.setVisibility(View.VISIBLE);
                itemBinding.listcellCheckbox.setTag(position); // This line is important.
 
                // 체크 박스 클릭하면 CheckBoxState 에 반영한다. setOnCheckedChangeListener 대신 사용
                itemBinding.listcellCheckbox.setOnClickListener(v -> {
                    if(getItem(position).getCheckBoxState() == true){
                        getItem(position).setCheckBoxState(false);
                        Log.d("checkbox","position : "+ position + " checkBoxState === "+ getItem(position).getCheckBoxState());
                    } else {
                        getItem(position).setCheckBoxState(true);
                        Log.d("checkbox","position : "+ position + " checkBoxState === "+ getItem(position).getCheckBoxState());
                    }
                });
 
                itemBinding.childLayout.setOnClickListener(v -> {
                    if(itemBinding.listcellCheckbox.isChecked() == false){
                        itemBinding.listcellCheckbox.setChecked(true);
                        getItem(position).setCheckBoxState(true);
                        //notifyDataSetChanged();
                        Log.d("checklist","position : "+ position + " checkBoxState === "+ getItem(position).getCheckBoxState());
                    } else {
                        itemBinding.listcellCheckbox.setChecked(false);
                        getItem(position).setCheckBoxState(false);
                        //notifyDataSetChanged();
                        Log.d("checklist","position : "+ position + " checkBoxState === "+ getItem(position).getCheckBoxState());
                    }
                });
            }
 
            // 재사용 문제 해결
            if(getItem(position).getCheckBoxState() == true){
                itemBinding.listcellCheckbox.setChecked(true);
                Log.d("ReUse","position : " + position + " checkBoxState === " + getItem(position).getCheckBoxState());
            } else {
                itemBinding.listcellCheckbox.setChecked(false);
                Log.d("ReUse","position : "+position + " checkBoxState ===" + getItem(position).getCheckBoxState());
            }
        }
    }
 
}
 
 

 

 

AddressActivity.java

public class AddressActivity extends AppCompatActivity implements AddressListAdapter.OnItemClickListener {
    private final String TAG = this.getClass().getSimpleName();
    Context mContext;
    private ActivityAddressBinding binding;
 
    private AddressViewModel viewModel;
 
    private ArrayList<Address_Item> addressItemList = new ArrayList<>(); // 서버 전체 데이터 리스트
    private ArrayList<Address_Item> searchItemList = new ArrayList<>(); // 검색한 데이터 리스트
    private RecyclerView mRecyclerView;
    private AddressListAdapter mAdapter;
 
    public static ConstraintLayout constraintLayout;
    public static boolean isCheckFlag = false;
    public CheckBox checkAll;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //setContentView(R.layout.activity_address);
        binding = ActivityAddressBinding.inflate(getLayoutInflater());
        View view = binding.getRoot();
        setContentView(view);
 
        mContext = AddressActivity.this;
        initView();
    }
 
    private void initView() {
        // 동기식 데이터 처리에서는 순서가 중요함.
        viewModelProvider();
        buildRecyclerView();
        getAddressList(); // 서버 데이터 가져오기
        setButtons();
    }
 
    private void buildRecyclerView() {
        mRecyclerView = binding.rvAddress;
        mRecyclerView.setHasFixedSize(true);
        LinearLayoutManager manager = new LinearLayoutManager(mContext);
 
        //mAdapter = new AddressViewAdapter(mContext,searchItemList); // 객체 생성
        mAdapter = new AddressListAdapter(mContext, Address_Item.itemCallback); // DiffUtil을 넣은 어댑터를 생성
        //mAdapter.submitList(searchItemList); // 서버에서 데이터를 가져온 이 후 코드에 추가하면 된다.
 
        DividerItemDecoration decoration = new DividerItemDecoration(mContext,manager.getOrientation());
        mRecyclerView.addItemDecoration(decoration);
        mRecyclerView.setLayoutManager(manager);
        mRecyclerView.setAdapter(mAdapter);
 
        mAdapter.setOnItemClickListener(this);
    }
 
    private void viewModelProvider() {
        // viewModel init
        viewModel = new ViewModelProvider(this).get(AddressViewModel.class);
 
        // viewModel observe
        viewModel.get_liveData().observe(thisnew Observer<AddressResult>() {
            @Override
            public void onChanged(AddressResult addressResult) {
                if(addressResult.getStatus().contains("success")){
                    addressItemList.clear(); // 서버에서 가져온 데이터 초기화 (전체 List)
                    searchItemList.clear(); // 서버에서 가져온 데이터 초기화 (검색 List)
 
                    for(Address_Item item : addressResult.getAddrinfo()){
                        addressItemList.add(item);
                        searchItemList.add(item);
                    }
 
                    Log.e(TAG, "viewModel observe");
 
                    mAdapter.submitList(searchItemList);
 
                } else {
                    Utils.showAlert(mContext,addressResult.getStatus(),addressResult.getMessage());
                }
            }
        });
 
    }
 
    private void getAddressList() {
        String search = "";
        viewModel.getAllAddressData(search);
    }
 
    private void setButtons(){
        isCheckFlag = false;
        constraintLayout = binding.listviewConstraint2;
 
        binding.search.setSubmitButtonEnabled(false);
        binding.search.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
            @Override
            public boolean onQueryTextSubmit(String query) {
                // 문자열 입력을 완료했을 때 문자열 반환
                // 쿼리가 비어있을 때에는 호출되지 않는다.
                //viewModel.getAllAddressData(query); // 서버 재접속 후 데이터 가져오기
                return false;
            }
 
            @Override
            public boolean onQueryTextChange(String newText) {
                // 문자열이 변할 때마다 바로바로 문자열 반환
                filter(newText); // 전체 데이터 가져온 후 로컬에서 검색 처리
//                if(newText.trim().length() == 0) {
//                    viewModel.getAllAddressData("");
//                }
                return true;
            }
        });
 
        if (isCheckFlag == false) {
            constraintLayout.setVisibility(View.GONE);
        } else if (isCheckFlag == true) {
            constraintLayout.setVisibility(View.VISIBLE);
        }
 
        // all checkbox
        checkAll = binding.lvCheckboxAll;
        binding.lvCheckboxAll.setOnCheckedChangeListener((buttonView, isChecked) -> {
            if (checkAll.isChecked() == true) {
                mAdapter.selectAll(searchItemList.size());
            } else {
                mAdapter.unselectall(searchItemList.size());
            }
        });
 
        binding.btnCancel.setOnClickListener(v -> {
            isCheckFlag = false;
            constraintLayout.setVisibility(View.GONE);
            mAdapter.unselectall(searchItemList.size());
            checkAll.setChecked(false); // 전체 선택 체크박스 해제
        });
    }
 
    // Filter Class
    public void filter(String charText) {
        charText = charText.toLowerCase(Locale.getDefault());
        searchItemList.clear();
        if (charText.length() == 0) {
            searchItemList.addAll(addressItemList);
        } else {
            for (Address_Item wp : addressItemList) {
                if(Utils.isNumber(charText)){ // 숫자여부 체크
                    if(wp.getMobileNO().contains(charText) || wp.getTelNO().contains(charText)){
                        // 휴대폰번호 또는 사무실번호에 숫자가 포함되어 있으면
                        searchItemList.add(wp);
                    }
                } else {
                    String iniName = HangulUtils.getHangulInitialSound(wp.getUserNM(), charText);
                    if (iniName.indexOf(charText) >= 0) { // 초성검색어가 있으면 해당 데이터 리스트에 추가
                        searchItemList.add(wp);
                    } else if (wp.getUserNM().toLowerCase(Locale.getDefault()).contains(charText)) {
                        searchItemList.add(wp);
                    }
                }
            }
        }
        mAdapter.notifyDataSetChanged();
    }
 
    @Override
    public void onItemClicked(Address_Item item, int position) {
        Toast.makeText(mContext, "position : "+position + " clieck item name : "+item.getUserNM(), Toast.LENGTH_SHORT).show();
    }
}
 

 

Github Source Code : https://github.com/jsk005/JavaProjects/tree/master/recyclerview_mvvm

 

GitHub - jsk005/JavaProjects: 자바 기능 테스트

자바 기능 테스트. Contribute to jsk005/JavaProjects development by creating an account on GitHub.

github.com

 

블로그 이미지

Link2Me

,