Following android's architecture guide, I am experimenting with SavedStateHandle in my view model. So when I post any changes from a thread to the view model, it doesn't reflect on UI. However, without SavedStateHandle the UI changes reflect just fine.
public class UserViewModel extends ViewModel {
UserRepository userRepository;
private static final String USER_KEY = "user_key";
private SavedStateHandle mState;
public UserViewModel(SavedStateHandle savedStateHandle) {
mState = savedStateHandle;
this.userRepository = new UserRepository();
}
public LiveData<String> getUserId() {
return mState.getLiveData(USER_KEY, userRepository.getUserId());
}
public void setUserId(String value) {
mState.set(USER_KEY, value);
}
}
public class UserFragment extends Fragment {
private UserViewModel mViewModel;
public static UserFragment newInstance() {
return new UserFragment();
}
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container,
#Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.user_fragment, container, false);
}
#Override
public void onViewCreated(#NonNull #NotNull View view, #Nullable #org.jetbrains.annotations.Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mViewModel = new ViewModelProvider(requireActivity()).get(UserViewModel.class);
mViewModel.getUserId().observe(getViewLifecycleOwner(), s -> {
((TextView) view.findViewById(R.id.text_view_1)).setText(s);
});
Executors.newCachedThreadPool().submit(() -> {
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
mViewModel.setUserId("111111");
});
}
public UserViewModel getmViewModel() {
return mViewModel;
}
}
Main Activity
my view only has one framelayout with id sample_fragment
public class MainActivity extends AppCompatActivity {
private final ExecutorService executorService = Executors.newCachedThreadPool();
private UserViewModel userViewModel;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
userViewModel = new ViewModelProvider(this).get(UserViewModel.class);
if (savedInstanceState == null) {
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
UserFragment userFragment = new UserFragment();
transaction.replace(R.id.sample_fragment, userFragment);
transaction.commit();
executorService.submit(() -> {
try {
Thread.sleep(2000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
userViewModel.setUserId("1");
});
}
}
}
with this code, the changes don't reflect on UI. however when I replace the SaveStateHandle with MutableLiveData then a simple postValue changes the screen just fine. From what I have read, it seems the live data is changed only when the lifecycle is in some specific states (like CREATED).
You set a default value in your getUserId, but I don't see where you use postValue on the data in SavedStateHandle.
I would add a mUserId member and move the code in getUserId to the ViewModel constructor to make sure it's initialized before any write to it. Then make set and post methods:
LiveData<String> mUserId;
public UserViewModel(SavedStateHandle savedStateHandle) {
mState = savedStateHandle;
this.userRepository = new UserRepository();
mUserId = mState.getLiveData(USER_KEY, userRepository.getUserId());
}
public LiveData<String> getUserId() {
return mUserId;
}
public void setUserId(String value) {
mState.getLiveData(USER_KEY).setValue(value);
}
public void postUserId(String value) {
mState.getLiveData(USER_KEY).postValue(value);
}
Related
I have been struggling with one bug for a week now. I am a beginner android developer and I am trying to build MVVM app. The idea is to be gaming news but that does not really matter. Here are my 3 classes that are key to this bug.
My NewsFragment class:
FragmentNewsBinding binding;
NewsFragmentAdapter adapter;
Context context;
public NewsFragmentViewModel newsFragmentViewModel;
public NewsFragment() {
// Required empty public constructor
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
newsFragmentViewModel = new ViewModelProvider(this).get(NewsFragmentViewModel.class);
// Inflating the layout for this fragment
binding = FragmentNewsBinding.inflate(inflater, container, false);
newsFragmentViewModel.init();
setAdapter(container);
observeChanges();
return binding.getRoot();
}
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
}
private void setAdapter(ViewGroup container) {
context = container.getContext();
adapter = new NewsFragmentAdapter(newsFragmentViewModel.getItems().getValue(), context);
RecyclerView.LayoutManager linearLayoutManager = new LinearLayoutManager(context);
binding.recyclerViewNews.setLayoutManager(linearLayoutManager);
binding.recyclerViewNews.setAdapter(adapter);
}
public void observeChanges() {
newsFragmentViewModel.getItems().observe(getViewLifecycleOwner(), new Observer<List<Item>>() {
#Override
public void onChanged(List<Item> items) {
adapter.notifyDataSetChanged();
}
});
}
}
NewsFragmentViewModel class:
public class NewsFragmentViewModel extends ViewModel {
private MutableLiveData<List<Item>> mItems;
private NewsRepository mRepo;
// Empty constructor
public NewsFragmentViewModel() {
}
public void init() {
if (mItems != null) {
return;
}
mRepo = NewsRepository.getInstance();
mItems = mRepo.getItems();
}
public LiveData<List<Item>> getItems() {
return mItems;
}
}
And the repository class:
public class NewsRepository {
MutableLiveData<List<Item>> mItems;
private static NewsRepository instance;
public static NewsRepository getInstance() {
if (instance == null) {
instance = new NewsRepository();
}
return instance;
}
//Empty constructor
private NewsRepository() {
}
public MutableLiveData<List<Item>> getItems() {
mItems = new MutableLiveData<>();
ServiceApi.getInstance().getGamezoneMethods().getGamezoneNews().enqueue(new Callback<Rss>() {
#Override
public void onResponse(Call<Rss> call, Response<Rss> response) {
if (response.code() == 200) {
List<Item> items = response.body().getChannel().getItems();
mItems.postValue(items);
}
}
#Override
public void onFailure(Call<Rss> call, Throwable t) {
Log.d("Failed", "Error");
mItems.postValue(null);
}
});
return mItems;
}
}
Now, the main problem with this code is that in the repository when I do my request I successfully get the data and I think, I successfully push the data to the viewmodel. But when I try to get my data from the LiveData in the viewmodel class or in the fragment class I always get null, and because of the null problem I cannot set up an adapter properly and I cannot set up my onChanged() method. I am trying to get the data in the fragment with the classic getValue() method. Please somebody help me because I am losing my mind.
If you are confused with this code, here is link to my github repo.
I am working on an android project with three tabs (3 tabs are created using Fragment class).
I created one Async inner class in Mainactivity and calling that class from onCreate(), but nothing is happening in the Logcat. Can anyone suggest me Where to define Async class which is getting resource in JSON format. Any better way of doing this thing is also welcome.
package com.utb.iftekhar.cityweatherappsnapshot1;
public class Tab2Fragment extends Fragment {
private static final String TAG="Tab2Fragment";
private Button button;
private static final String CITY_NAME_SEARCHED="cityNameSearched";
private List<String> historyList;
private ListView historyListView;
private ArrayAdapter<String> arrayAdapter;
String cityNameSearched="";
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View view=inflater.inflate(R.layout.tab2_frag, container, false);
historyListView=view.findViewById(R.id.historyList);
historyList=new ArrayList<>();
if(getArguments()!=null){
cityNameSearched=getArguments().getString(CITY_NAME_SEARCHED);//Not able to set values to the ctiNameSearched variable.
}else
{
Log.i("No","Arguments");//
}
if(!(cityNameSearched==null) || !cityNameSearched.equals("")){
historyList.add(cityNameSearched);
Log.i("before", cityNameSearched);
Log.i("Value in tab2frag",cityNameSearched);
}
return view;
}
//This method is returning the searched city name searched int the edit text from fragment(Tab1Fragment) but not able to access this value in cityNameSearched variable.
public void updateEditText(String newText){
Log.i("received from 1 in 2",newText);
this.cityNameSearched=newText;
}
/*
#Override
public void onAttach(Context context) {
super.onAttach(context);
if(context instanceof Tab2FragmentListener){
tab2FragmentListener=(Tab2FragmentListener)context;
}else{
throw new RuntimeException(context.toString()+
" must implement Tab2FragmentListener"
);
}
}
#Override
public void onDetach() {
super.onDetach();
tab2FragmentListener=null;
}*/
}
public class Tab1Fragment extends Fragment{
private String weatherResults;
private Tab1FragmentListener tab1FragmentListener;
private String cityNameSearched;
public interface Tab1FragmentListener{
void onInput1Set(String input);
}
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container,
#Nullable Bundle savedInstanceState) {
View view=inflater.inflate(R.layout.tab1_frag, container, false);
getWeatherButton=(Button)view.findViewById(R.id.getWeatherButton);
searchCityByName=(EditText)view.findViewById(R.id.searchCityByName);
weatherResultTextView=(TextView)view.findViewById(R.id.weatherResult);
mainActivity=new MainActivity();
getWeatherButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
downloadTask=new DownloadTask(new DownloadTask.AsyncResponse() {
#Override
public void processFinish(String output) {
if(!output.equals("")){
weatherResultTextView.setText(output);
}else{
weatherResultTextView.setText("");
}
}
});
cityNameSearched=searchCityByName.getText().toString().trim();
tab1FragmentListener.onInput1Set(cityNameSearched);
Log.i("CityNameSearched", cityNameSearched);
try {
String encodedCityName= URLEncoder.encode(cityNameSearched,"UTF-8" );
} catch (Exception e) {
e.printStackTrace();
}
downloadTask.execute("https://openweathermap.org/data/2.5/weather?q="+cityNameSearched+"&appid=b6907d289e10d714a6e88b30761fae22");
Log.i("Button Cliked","Clicked");
InputMethodManager mgr=(InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
mgr.hideSoftInputFromWindow(searchCityByName.getWindowToken(), 0);
}
});
return view;
}
#Override
public void onAttach(Context context) {
super.onAttach(context);
if(context instanceof Tab1FragmentListener){
tab1FragmentListener=(Tab1FragmentListener)context;
}else{
throw new RuntimeException(context.toString()+
" must implement Tab1FragmentListener"
);
}
}
#Override
public void onDetach() {
super.onDetach();
tab1FragmentListener=null;
}
public void updateEditText(String newText){
searchCityByName.setText(newText);
}
}
public class MainActivity extends AppCompatActivity implements Tab1Fragment.Tab1FragmentListener {
private static final String TAG="MainActivity";
private SectionsPageAdapter mySectionPageAdapter;
private ViewPager viewPager;
private DataForTabs dataForTabs;
private Tab1Fragment tab1Fragment;
private Tab2Fragment tab2Fragment;
String input="";
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d(TAG,"onCreate: Starting.");
tab1Fragment=new Tab1Fragment();
Log.i(" From 1 to main ", input);//No Log appears for this input variable
mySectionPageAdapter=new SectionsPageAdapter(getSupportFragmentManager());
//set up the ViewPager with the sections adaptor
viewPager=(ViewPager)findViewById(R.id.container);
setupViewPager(viewPager);
TabLayout tabLayout=(TabLayout)findViewById(R.id.tabs);
tabLayout.setupWithViewPager(viewPager);
}
public void setupViewPager(ViewPager viewPager){
SectionsPageAdapter adapter=new
SectionsPageAdapter(getSupportFragmentManager());
adapter.addFragment(new Tab1Fragment(),"Home");
adapter.addFragment(new Tab2Fragment(),"History");
adapter.addFragment(new Tab3Fragment(),"About");
viewPager.setAdapter(adapter);
}
#Override
public void onInput1Set(String input) {
tab2Fragment.updateEditText(input);
this.input=input;
}
}
I am learning Room DataBase!!
I Know How to Insert and Retrieve Data from Room DataBase to Recycler View!! But In Delete Operation I am getting error of "No Adapter attached Skipped Layout!"
What I want when anyone click on delete button on recycler view . The Task should be deleted
That's why I also Used delete method in Interface and add Interface in Recycler View Adapter which give call back to MainActivity so that we delete and update the recycler view
All of my codes are given below
Here is my Entity Class named as Task
#Entity
public class Task implements Serializable {
#PrimaryKey(autoGenerate = true)
private int id;
#ColumnInfo(name = "task_name")
private String task_name;
#ColumnInfo
private String task_desc;
#ColumnInfo
private String comment;
#ColumnInfo
private String task_comp_date;
#ColumnInfo
private String activate;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getTask_name() {
return task_name;
}
public void setTask_name(String task_name) {
this.task_name = task_name;
}
public String getTask_desc() {
return task_desc;
}
public void setTask_desc(String task_desc) {
this.task_desc = task_desc;
}
public String getTask_comp_date() {
return task_comp_date;
}
public void setTask_comp_date(String task_comp_date) {
this.task_comp_date = task_comp_date;
}
public String getComment() {
return comment;
}
public void setComment(String comment) {
this.comment = comment;
}
public String getActivate() {
return activate;
}
public void setActivate(String activate) {
this.activate = activate;
}
}
My Data Accession Object named as TaskDao
#Dao
public interface TaskDao {
#Query("SELECT * FROM task")
List<Task> getAll();
#Insert
void insert(Task task);
#Delete
void delete(Task task);
#Update
void update(Task task);
}
My DataBase
#Database(entities = {Task.class},version = 1)
public abstract class AppDataBase extends RoomDatabase {
public abstract TaskDao taskDao();
}
My DataBaseClient named as DatabaseClient
public class DatabaseClient {
private Context context;
private static DatabaseClient mInstace;
private AppDataBase appDataBase;
public DatabaseClient(Context context) {
this.context = context;
appDataBase = Room.databaseBuilder(context,AppDataBase.class,"MyDailyTask").build();
}
public static synchronized DatabaseClient getInstance(Context context)
{
if(mInstace == null)
{
mInstace = new DatabaseClient(context);
}
return mInstace;
}
public AppDataBase getAppDataBase()
{
return appDataBase;
}
}
My RecyclerView Adapter
public class TaskAdapter extends RecyclerView.Adapter<TaskAdapter.MyViewHolder> {
private Context context;
private List<Task> taskList;
public interface OnDeleteClickListener
{
void OnDeleteClickListener(Task task);
}
private OnDeleteClickListener onDeleteClickListener;
public TaskAdapter(Context context, List<Task> taskList) {
this.context = context;
this.taskList = taskList;
}
public void setOnDeleteClickListener(OnDeleteClickListener onDeleteClickListener) {
this.onDeleteClickListener = onDeleteClickListener;
}
#NonNull
#Override
public MyViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view;
view = LayoutInflater.from(context).inflate(R.layout.view_task_list,parent,false);
MyViewHolder myViewHolder = new MyViewHolder(view);
return myViewHolder;
}
#Override
public void onBindViewHolder(#NonNull MyViewHolder holder, int position) {
holder.setData(taskList.get(position).getTask_name(),taskList.get(position).getTask_desc(),taskList.get(position).getComment(),taskList.get(position).getTask_comp_date(),position);
}
#Override
public int getItemCount() {
return taskList.size();
}
class MyViewHolder extends RecyclerView.ViewHolder {
private TextView t1,t2,t3,t4,t5;
private ImageView view;
public MyViewHolder(#NonNull View itemView) {
super(itemView);
t1= itemView.findViewById(R.id.tvCommnt);
t2=itemView.findViewById(R.id.tvDesc);
t3=itemView.findViewById(R.id.tvName);
t4= itemView.findViewById(R.id.tvStart);
t5 = itemView.findViewById(R.id.tvEnd);
view =itemView.findViewById(R.id.tvdele);
}
public void setData(String t01, String t02, String t03, String t05, final int position)
{
t1.setText("Task Comment "+t03);
t2.setText("Task Description "+t02);
t3.setText("Task Name "+ t01);
t4.setText("Start ");
t5.setText("Ënd "+t05);
view.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(onDeleteClickListener!=null)
{
onDeleteClickListener.OnDeleteClickListener(taskList.get(position));
taskList.remove(position);
notifyDataSetChanged();
}
}
});
}
}
}
MainActivity.java :
public class MainActivity extends AppCompatActivity {
private RecyclerView recyclerView;
ImageView imageView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
imageView = findViewById(R.id.ivAdd);
recyclerView = findViewById(R.id.rvTask);
imageView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
startActivity(new Intent(getApplicationContext(), AddTaskActivity.class));
}
});
new newTask().execute();
}
class newTask extends AsyncTask<Void,Void, List<Task>> implements TaskAdapter.OnDeleteClickListener {
List<Task> tasks;
#Override
protected List<Task> doInBackground(Void... voids) {
tasks = DatabaseClient.getInstance(getApplicationContext()).getAppDataBase().taskDao().getAll();
return tasks;
}
#Override
protected void onPostExecute(List<Task> tasks) {
super.onPostExecute(tasks);
TaskAdapter taskAdapter = new TaskAdapter(MainActivity.this,tasks);
LinearLayoutManager layoutManager = new LinearLayoutManager(MainActivity.this);
recyclerView.setLayoutManager(layoutManager);
recyclerView.addItemDecoration(new DividerItemDecoration(getApplicationContext(),DividerItemDecoration.VERTICAL));
recyclerView.setAdapter(taskAdapter);
taskAdapter.setOnDeleteClickListener(this);
}
#Override
public void OnDeleteClickListener(final Task task) {
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
DatabaseClient.getInstance(getApplicationContext()).getAppDataBase().taskDao().delete(task);
}
},1000);
}
}
welcome in stack
you interface is declared but not assign so your will throw null pointer when click on item inside your list, but good work your check null before fire method interface
first add setter for OnDeleteClickListener inside your adapter
public void setOnDeleteClickListener(OnDeleteClickListener listener){
this.onDeleteClickListener=listener;
}
and also add code that remove item from you list when user click item
so inside setData method update this code
view.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (onDeleteClickListener != null) {
onDeleteClickListener.OnDeleteClickListener(taskList.get(position));
//remove item from list and then notify adapter data is changed
taskList.remove(position);
notifyDataSetChanged();
}
}
});
finally to Triggers onDeleteClickListener inside your AsyncTask
update your code here
#Override
protected void onPostExecute(List < Task > tasks) {
super.onPostExecute(tasks);
TaskAdapter taskAdapter = new TaskAdapter(MainActivity.this, tasks, this);
LinearLayoutManager layoutManager = new LinearLayoutManager(MainActivity.this);
recyclerView.setLayoutManager(layoutManager);
recyclerView.addItemDecoration(new DividerItemDecoration(getApplicationContext(), DividerItemDecoration.VERTICAL));
recyclerView.setAdapter(taskAdapter);
//pass this that refer to my interface
taskAdapter.setOnDeleteClickListener(this);
}
inside your OnDelete just use this code to run in another thread
AsyncTask.execute(new Runnable() {
#Override
public void run() {
DatabaseClient.getInstance(getApplicationContext()).getAppDataBase().taskDao().delete(task);
}
});
Advice : Don't name your variable or arguments like String
t01,String t02..etc ,choice name for like what this variable jop
like String taskComment,String taskName ..etc
i hope this help you
I am following the clean architecture with mvvm. When I injcet viewmodel factory in fragment and try to execute use case from fragment viewmodel it is giving NPE on use case. I have injected the use case as well.
Please look into the code and point out the mistake
MainModule.java
#Module
public class MainModule {
#Provides
LoginViewModel provideLoginViewModel() {
return new LoginViewModel();
}
#Provides
#Named("LoginActivity")
ViewModelProvider.Factory provideActivityViewModelFactory(LoginViewModel loginViewModel) {
return new ViewModelProviderFactory<>(loginViewModel);
}
#Provides
PasscodeViewModel providePasscodeViewModel(SetPasscode setPasscode) {
return new PasscodeViewModel(setPasscode);
}
#Provides
#Named("PasscodeFragment")
ViewModelProvider.Factory provideFragmentViewModelFactory(PasscodeViewModel passcodeViewModel) {
return new ViewModelProviderFactory<>(passcodeViewModel);
}
}
PasscodeFragment.java
public class PasscodeFragment extends BaseFragment<PasscodeViewModel> implements PasscodeNavigator, View.OnClickListener {
#Inject
#Named("PasscodeFragment")
ViewModelProvider.Factory viewModelFactory;
PasscodeViewModel passcodeViewModel;
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
passcodeViewModel.setNavigator(this);
}
public PasscodeFragment() {
}
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
((MainApplication) getActivity().getApplicationContext()).getComponent().inject(this);
View view = inflater.inflate(R.layout.fragment_passcode, container, false);
initViews(view);
return view;
}
#Override
public PasscodeViewModel getViewModel() {
passcodeViewModel = ViewModelProviders.of(this, viewModelFactory).get(PasscodeViewModel.class);
return passcodeViewModel;
}
PasscodeViewModel.java (HERE THE SETPASSCODE IS COMING NULL)
public class PasscodeViewModel extends BaseViewModel<PasscodeNavigator> {
//use case
private SetPasscode setPasscode;
//data
private String tempPin;
private boolean isConfirming = false;
public PasscodeViewModel() {
}
public PasscodeViewModel(SetPasscode setPasscode) {
this.setPasscode = setPasscode;
}
public void checkForConfirmation(String passcode) {
try {
if (passcode.length() == 4) {
if (isConfirming) {
//second attempt
if (tempPin.equals(passcode)) {
//done
Log.e("ganesh", passcode);
setPasscode(passcode);
} else {
//reset
isConfirming = false;
tempPin = "";
getNavigator().onPinNotMatched();
getNavigator().clearViewsForFirstAttempt();
}
} else {
//first attempt
tempPin = passcode;
isConfirming = true;
getNavigator().clearViewsForSecondAttempt();
}
}
} catch (NumberFormatException e) {
getNavigator().wrongPinFormat();
getNavigator().clearViewsForFirstAttempt();
}
}
private void setPasscode(String passcode) {
//setPasscode IS NULL HERE
setPasscode.execute(new DisposableSingleObserver<Boolean>() {
#Override
public void onSuccess(Boolean aBoolean) {
if (aBoolean)
getNavigator().onPinSetSuccess();
else
getNavigator().onPinSetFailed();
}
#Override
public void onError(Throwable e) {
getNavigator().onPinSetFailed();
}
}, SetPasscode.Params.setPasscode(passcode));
}
#Override
protected void onCleared() {
if (setPasscode != null)
setPasscode.dispose();
}
}
SetPasscode.java
public class SetPasscode extends SingleUseCase<Boolean, SetPasscode.Params> {
private UserRepository repository;
public SetPasscode(PostExecutionThread postExecutionThread, UserRepository repository) {
super(postExecutionThread);
this.repository = repository;
}
#Override
public Single<Boolean> buildUseCaseObservable(SetPasscode.Params params) {
return repository.setPasscode(params.passcode);
}
public static final class Params {
private String passcode;
private Params(String passcode) {
this.passcode = passcode;
}
public static SetPasscode.Params setPasscode(String passcode) {
return new SetPasscode.Params(passcode);
}
}
}
LoginActivity.java
public class LoginActivity extends BaseActivity<LoginViewModel> implements LoginNavigator {
#Inject
#Named("LoginActivity")
ViewModelProvider.Factory viewModelFactory;
LoginViewModel loginViewModel;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
getSupportActionBar().hide();
((MainApplication) getApplicationContext()).getComponent().inject(this);
loginViewModel = ViewModelProviders.of(this, viewModelFactory).get(LoginViewModel.class);
loginViewModel.setNavigator(this);
initViews();
}
#Override
public LoginViewModel getViewModel() {
return loginViewModel;
}
I have a RecyclerView in a Fragment where I would like to populate data using MutableLiveData. I have a mock DAO that returns a list of objects of my Model class. The RecyclerView is blank when executed.
This is my mock DAO class:
public class DaoMockFriends implements DaoFriendsInterface {
private ArrayList<FriendsModel> friendsModelsArrayList;
public DaoMockFriends() {
friendsModelsArrayList = new ArrayList<>();
}
#SuppressLint("NewApi")
#Override
public List<FriendsModel> getFriendsList() {
for(int i = 0 ; i < 10 ; i++){
FriendsModel friendsModel = (new FriendsModel("adasdsada"+i,"sdadsadsa"+(i*2),"adsdsadssad"+(i*3),""+(i++), LocalDateTime.now()));
friendsModelsArrayList.add(friendsModel);
}
return friendsModelsArrayList;
}
}
This is my ViewModel class:
import android.arch.lifecycle.LiveData;
import android.arch.lifecycle.MutableLiveData;
import android.arch.lifecycle.ViewModel;
import java.util.List;
public class FriendsListLiveDataProvider extends ViewModel {
private MutableLiveData<List<FriendsModel>> friendsModelLiveData = new MutableLiveData<>();
public LiveData<List<FriendsModel>> getFriendsModelLiveData() {
if(this.friendsModelLiveData == null) friendsModelLiveData.postValue(new DaoMockFriends().getFriendsList());
return this.friendsModelLiveData;
}
}
This is my RecyclerViewAdapter class:
public class AdapterFriendsListRecyclerView extends RecyclerView.Adapter<AdapterFriendsListRecyclerView.ViewHolder> {
private List<FriendsModel> listOfFriends;
public AdapterFriendsListRecyclerView(List<FriendsModel> listOfFriends) {
this.listOfFriends = listOfFriends;
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new ViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.friend_entiry_block,parent,false));
}
#SuppressLint("SetTextI18n")
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
FriendsModel friendsInterface = this.listOfFriends.get(position);
String firstName = friendsInterface.getFirstName();
String lastName = friendsInterface.getLastName();
holder.getTextView().setText(firstName+" "+lastName);
}
#Override
public int getItemCount() {
return listOfFriends.size();
}
public void addItems(List<FriendsModel> friendsInterface){
this.listOfFriends = friendsInterface;
notifyDataSetChanged();
}
public static class ViewHolder extends RecyclerView.ViewHolder {
private TextView textView;
public ViewHolder(View itemView) {
super(itemView);
this.textView = itemView.findViewById(R.id.friendEntityNameTV);
}
public TextView getTextView() {
return textView;
}
}
}
This is the implementation in the Fragment :
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View v = inflater.inflate(R.layout.fragment_friends_list, container, false);
friendsListRV = v.findViewById(R.id.FriendsListRecyclerView);
adapterFriendsListRecyclerView = new AdapterFriendsListRecyclerView(new ArrayList<>());
friendsListRV.setLayoutManager(new LinearLayoutManager(getActivity()));
friendsListRV.setAdapter(adapterFriendsListRecyclerView);
friendsListLiveDataProvider = ViewModelProviders.of(this).get(FriendsListLiveDataProvider.class);
friendsListLiveDataProvider
.getFriendsModelLiveData()
.observe(this, (friendsModels) -> adapterFriendsListRecyclerView
.addItems(friendsModels));
return v;
}
I think that somehow the data from the DAO isn't getting across to the RecyclerViewAdapter. I followed this blog.
How to make the data from the DAO visible in the RecyclerView using LiveData
EDIT:
I changed my ViewModel class as per Yogesh's answer. But my app terminates abruptly without any error or warning messages. When I debug, I found that my app crashes on execution of this line: friendsModelLiveData.postValue(new DaoMockFriends().getFriendsList());
EDIT2:
I tried with the code below in my ViewModel implementation class
public LiveData<List<FriendsModel>> getFriendsModelLiveData() {
if(friendsModelLiveData == null){
friendsModelLiveData = new MutableLiveData<>();
}
loadData();
return this.friendsModelLiveData;
}
private void loadData(){
DaoMockFriends anInterface = new DaoMockFriends();
ArrayList<FriendsModel> fm = anInterface.getFriendsList();
ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.submit(new Runnable() {
#Override
public void run() {
friendsModelLiveData.postValue(fm);
}
});
}
The app abruptly terminates at this ArrayList<FriendsModel> fm = anInterface.getFriendsList(); line. I followed this thread to come up with this type of approach.
Method new DaoMockFriends().getFriendsList() never gets executed, because you have initialized friendsModelLiveData at declaration.
Update:
public class FriendsListLiveDataProvider extends ViewModel {
private MutableLiveData<List<FriendsModel>> friendsModelLiveData;
public LiveData<List<FriendsModel>> getFriendsModelLiveData() {
if(this.friendsModelLiveData == null) {
friendsModelLiveData = new MutableLiveData<>();
friendsModelLiveData.postValue(new DaoMockFriends().getFriendsList());
}
return this.friendsModelLiveData;
}
}