hu... 저의 첫 글쓰기네요... 잠 못드는 새벽 떨리는 마음을 가라앉히며 안드로이드 개발 중 겪었던 문제들과 해결한 방식들을 토대로 몇 개의 글들을 작성해보려 합니다!
오늘은 몇 아이들(=문제들..‼️😱)중 실시간으로 데이터를 보이는 문제를 겪었고, LiveData를 사용해 문제를 해결했던 방식에 대해서 설명드리도록 하겠습니다.
지금부터 저의 작은 이야기, 시작해보겠습니다.(두둥...짝👻)
[시나리오 출연 Fragment 및 Activity]
A_fragment | B_Activity |
listView를 포함하고 있다. listView의 각 Item에는 좋아요 개수 및 댓글 개수가 있다. postAdapter를 통해 데이터를 recyclerView와 연결한다. |
listView의 각 Item의 상세 정보를 보여준다. A_fragent와 마찬가지로 좋아요 개수 및 댓글 개수를 표시한다. |
[원했던 동작]
1. A_fragment의 댓글 버튼을 클릭시 B_Activity로 전환되며 A_fragment가 가진 좋아요 개수 및 댓글 개수를 동일하게 표시한다.
2. B_Activity에서 좋아요 혹은 댓글을 달아 그 수가 증가하고, A_fragment로 돌아간 경우 A_fragment와 B_Activity는 동일한 좋아요 개수 및 댓글 개수를 표시한다.
=> 즉, A_fragment , B_Activity 둘 중 어느곳에서든 데이터를 증가 혹은 감소 시키던 간에 두 레이아웃 모두에서 같은 수를 화면에 보여야 하는 것입니다.
하지만...마음이 아프게도 B_Activity에서 버튼을 클릭하고(수를 증가시키고) A_fragment에 왔을 경우 데이터가 즉각 적으로 변경되지 않았습니다. A_fragment와 B_Activity가 서로 다른 데이터를 나타냈쬬..ㅠㅜ
notifyDataSetChanged(), OnResume등의 방법 또한 사용해보았지만, 이러한 방식을 사용하는 경우 모든 데이터가 새로 불러와 졌기 때문에 비용소모가 어마어마어마 하더군요!
그래서 prof.google에게 여쭤보다보니...LiveData 라는 예쁜 기능을 발견하게 되었고 이를 적용시켜 보았습니다.
사용 방법은 이렇습니다.
1. 우선 두 객체에서 함께 바라볼 데이터를 관리하는 PostViewModel Java Class 를 정의합니다.
해당 클래스에서는 라이브데이터를 정의하고 생성하며 데이터의 변화를 감지합니다.
[PostViewModel.Java]
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import java.util.ArrayList;
import java.util.List;
public class PostViewModel extends ViewModel {
public static MutableLiveData<List<Post>> post = new MutableLiveData<>();
public MutableLiveData<List<Post>> getPost() {
if (post == null) {
post = new MutableLiveData<>();
}
return post;
}
public static void setLike(int pos, int val) {
List<Post> p = new ArrayList<>();
p.addAll(post.getValue());
p.get(pos).setLike(p.get(pos).getLike() + (val));
post.setValue(p);
}
}
🔎 주의해야 할 점 🔎
Data를 Update할 때는 setValue()를 통해서 작업해 주어야 데이터가 변경됨을 알아챌 수 있답니다. ⭐️(안드로이드 공홈 참고)
2. setValue()를 통해 초기 데이터를 세팅해줍니다.
PostMain Fragment== A_fragment...! 시나리오 속 주인공 입니다 ㅎㅎ list view를 가지고 있으며! postAdapter를 통해 데이터를 연결시켜 줍니다. PostViewModel의 객체가 여기서 생성된 이유는 바로 데이터를 세팅 해주고 싶었기 때문이죠 ㅎㅎ
[PostMain.Java]
public class PostMain extends Fragment {
//데이터를 받아올 Lists
private ArrayList<Post> posts;
private RecyclerView listView;
private PostAdapter postAdapter;
private PostViewModel postViewModel;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
//fragment_post 내에 정의 된 listView_p의 recyclerView를 불러옵니다.
ViewGroup root = (ViewGroup) inflater.inflate(R.layout.fragment_post, container, false);
listView = root.findViewById(R.id.listview);
setListView(listView);
return root;
}
public void setListView(RecyclerView listView) {
//서버에서 데이터 받아오기
service.getPost().enqueue(new Callback<PostResponse>() {
@Override
public void onResponse(Call<PostResponse> call, Response<PostResponse> response) {
if (response.isSuccessful()) {
PostResponse p = response.body();
//ViewModel 객체 생성
postViewModel = new ViewModelProvider(requireActivity()).get(PostViewModel.class);
//post 데이터 및 like 데이터 받아오기
posts = p.getPostData();
//ViewModel의 데이터 setting
postViewModel.getPost().setValue(posts);
setPostAdapter(listView);
}
}
@Override
public void onFailure(Call<PostResponse> call, Throwable t) {
//에러 처리
}
});
}
private void setPostAdapter(RecyclerView listView) {
//listView adapter 적용
postAdapter = new ListPostAdapter(getActivity(), post);
listView.setAdapter(postAdapter);
LinearLayoutManager pLayoutManager = new LinearLayoutManager(getActivity());
pLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
listView.setLayoutManager(pLayoutManager);
listView.setItemAnimator(new DefaultItemAnimator());
}
}
3. 이벤트 처리 1
해당 Adapter는 fragment의 listView에 보여질 데이터를 연결시켜주는 역할을 합니다.
listItem안에 있는 버튼을 클릭하기 때문에 해당 버튼클릭 이벤트는 Adapter로 전달됩니다. 리스너를 통해 해당 이벤트를 전달 받고, PostViewModel의 함수에 접근해 LiveData를 Update 해줍니다.
그러면 데이터가 바뀜을 감지하고 새로운 값으로 데이터를 변경하게 되는 것이죠.
하지만 여기서 끝나면 안되죠!
저희는 B_Activity에서 값이 변경되면 A_fragment에서도 똑같은 값을 바라보기를 원합니다!
그럼 B_Activity에서도 ViewModel에 접근해서 값을 변경해주면 되겠죠?!
그럼 A_fragment의 Observer가 알아채고 값을 변경해줄 테니까요 ㅎㅎㅎ
[PostAdapter.Java]
public class PostAdapter extends RecyclerView.Adapter<PostAdapter.MyViewHolder> {
private ArrayList<Post> post = new ArrayList<>();
private PostViewModel postViewModel;
private Context context;
public PostAdapter(Context context) {
this.context = context;
}
@NonNull
@Override
public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.listitem_post, parent, false);
//ViewModel 객체 생성
postViewModel = new ViewModelProvider((ViewModelStoreOwner) context).get(PostViewModel.class);
return new MyViewHolder(itemView);
}
@Override
public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
setViewModel(holder, position);
setPostData(holder, position);
}
private void setViewModel(MyViewHolder holder, int position) {
//PostViewModel에 있는 데이터가 변경이 됐음을 감지하면(setValue()를 통해 데이터의 변경이 일어남) onChanged 함수실행
postViewModel.getPost().observe((LifecycleOwner) context, new Observer<List<Post>>() {
@Override
public void onChanged(List<Post> p) {
//변경된 값으로 holder의 데이터 변경
holder.LikedNum.setText(String.valueOf(p.get(position).getLikeNum()));
}
});
}
private void setPostData(MyViewHolder holder, int position) {
Post post = postViewModel.getPost().getValue().get(position);
holder.likedNum.setText(String.valueOf(post.getLikeNum()));
setClickListenerOnHolder(holder, position);
}
private void setClickListenerOnHolder(MyViewHolder holder, int position) {
holder.img_like.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (!v.isActivated()) {
PostViewModel.setLike(position, 1);
} else {
PostViewModel.setLike(position, -1);
}
}
});
holder.btn_detail.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//gotoDetailPostActivity
intent = new Intent(context, DetailPostActivity.class);
intent.putExtra("pos", position);
((Activity) context).startActivity(intent);
}
});
}
@Override
public int getItemCount() {
return post.size();
}
class MyViewHolder extends RecyclerView.ViewHolder {
TextView likedNum;
Button btn_detail;
ImageView img_like;
MyViewHolder(View view) {
super(view);
LikedNum = (TextView) view.findViewById(R.id.likedNum);
btn_detail=(Button)view.findViewById(R.id.btn_detail);
img_like=(ImageView)view.findViewById(R.id.img_like;
}
}
}
4. 이벤트처리 2
자 위와 동일하게 postViewModel의 객체를 생성해줍니다. 좋아요를 클릭했을 때 postViewModel의 함수를 호출하여 값을 변경해줌으로써 observe하고 있는 곳들에 이벤트가 전달 될 수 있도록 합니다. PostAdapter에서와 같이 DetailPostActivity도 Observe를 통해 관찰하고 값을 변경해줄 수 있지만, 많은 내용을 담고 있지 않기 때문에 다시 함수(setPostDetail())를 호출함으로써 값을 변경하는 것을 확인할 수 있습니다.
[DetailPostActivity]
public class DetailPostActivity extends AppCompatActivity {
private static final String TAG = "postDetail";
private PostViewModel postViewModel;
private Button btn_like;
private Post post;
TextView likeNum;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_detail_post);
setPostDetail();
}
private void setPostDetail() {
Intent in = getIntent();
int position = (int) intent.getExtras().get("pos");
postViewModel = new ViewModelProvider((ViewModelStoreOwner) this).get(PostViewModel.class);
postLikeNum = (TextView) findViewById(R.id.likeNum);
postLikeNum.setText(String.valueOf(postViewModel.getPost().getValue().get(position).getLikeNum()));
postLikedImage.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (!v.isActivated()) {
PostViewModel.setLike(position, 1);
} else {
PostViewModel.setLike(position, -1);
}
setPostDetail();
}
});
}
}
많은 도움이 되었길 바랍니다 :)
ha...라이브데이터 적용법만 안다면 정말 유용한 친구 입니다!>_<
다들 개발 화이팅 안녕🌻
참고 사이트 및 블로그
developer.android.com/topic/libraries/architecture/livedata?hl=ko
developer.android.com/topic/libraries/architecture/viewmodel?hl=ko
'Front-End 🧚🏻 > Android' 카테고리의 다른 글
[안드로이드(Android)-JAVA] Material Design Icons 적용 방법과 색상 변경 (1) | 2020.12.02 |
---|---|
[안드로이드(Android)-JAVA] 내부저장소에 비트맵 저장하여 캐시 구현하는 방법 (0) | 2020.11.30 |
댓글