Last Updated
Viewed 07 Times

The structure of the app is sort of simple. I have a fragment, which invokes a dialoguefragment, after the data is received, i want to send it to another fragment, by using a viewmodel(which i found the simplest to use). In the second fragment, the data is received, and then finally passed to a recycleviewadapter, to display the data. I am getting error.

E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.example.newvibe, PID: 10141
    java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String com.example.newvibe.Book.getTitle()' on a null object reference
        at com.example.newvibe.RecyclerViewAdapter.onBindViewHolder(RecyclerViewAdapter.java:45)
        at com.example.newvibe.RecyclerViewAdapter.onBindViewHolder(RecyclerViewAdapter.java:15)
        at androidx.recyclerview.widget.RecyclerView$Adapter.onBindViewHolder(RecyclerView.java:6781)
        at androidx.recyclerview.widget.RecyclerView$Adapter.bindViewHolder(RecyclerView.java:6823)
        at androidx.recyclerview.widget.RecyclerView$Recycler.tryBindViewHolderByDeadline(RecyclerView.java:5752)
        at androidx.recyclerview.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:6019)
        at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:5858)
        at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:5854)
        at androidx.recyclerview.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:2230)
        at androidx.recyclerview.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1557)
        at androidx.recyclerview.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1517)
        at androidx.recyclerview.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:612)
        at androidx.recyclerview.widget.RecyclerView.dispatchLayoutStep2(RecyclerView.java:3924)
        at androidx.recyclerview.widget.RecyclerView.onMeasure(RecyclerView.java:3336)
        at android.view.View.measure(View.java:23169)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6749)
        at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1535)
        at android.widget.LinearLayout.measureHorizontal(LinearLayout.java:1187)
        at android.widget.LinearLayout.onMeasure(LinearLayout.java:706)
        at android.view.View.measure(View.java:23169)
        at androidx.viewpager.widget.ViewPager.onMeasure(ViewPager.java:1638)
        at android.view.View.measure(View.java:23169)
        at android.widget.LinearLayout.measureVertical(LinearLayout.java:978)
        at android.widget.LinearLayout.onMeasure(LinearLayout.java:704)
        at android.view.View.measure(View.java:23169)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6749)
        at android.widget.FrameLayout.onMeasure(FrameLayout.java:185)
        at androidx.appcompat.widget.ContentFrameLayout.onMeasure(ContentFrameLayout.java:143)
        at android.view.View.measure(View.java:23169)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6749)
        at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1535)
        at android.widget.LinearLayout.measureVertical(LinearLayout.java:825)
        at android.widget.LinearLayout.onMeasure(LinearLayout.java:704)
        at android.view.View.measure(View.java:23169)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6749)
        at android.widget.FrameLayout.onMeasure(FrameLayout.java:185)
        at android.view.View.measure(View.java:23169)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6749)
        at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1535)
        at android.widget.LinearLayout.measureVertical(LinearLayout.java:825)
        at android.widget.LinearLayout.onMeasure(LinearLayout.java:704)
        at android.view.View.measure(View.java:23169)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6749)
        at android.widget.FrameLayout.onMeasure(FrameLayout.java:185)
        at com.android.internal.policy.DecorView.onMeasure(DecorView.java:716)
        at android.view.View.measure(View.java:23169)
        at android.view.ViewRootImpl.performMeasure(ViewRootImpl.java:2718)
        at android.view.ViewRootImpl.measureHierarchy(ViewRootImpl.java:1572)
        at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1855)
        at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1460)
        at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:7183)
E/AndroidRuntime:     at android.view.Choreographer$CallbackRecord.run(Choreographer.java:949)
        at android.view.Choreographer.doCallbacks(Choreographer.java:761)
        at android.view.Choreographer.doFrame(Choreographer.java:696)
        at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:935)
        at android.os.Handler.handleCallback(Handler.java:873)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:193)
        at android.app.ActivityThread.main(ActivityThread.java:6669)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
Process 10141 terminated.

I am passing a custom book object, which has the method getTitle, because of the null object error, I figure no book is getting passed. After browsing the web, i figured that a possible error might be that i pass this book OnCreateView instead of OnActivityCreated, but to my understanding, I cannot instantiate the whole recyclerview and layoutmanager etc. onActivityCreated.

This is the first Fragment

   import android.os.Bundle;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.view.ViewGroup;
    import android.widget.Button;
    import android.widget.Toast;


    import androidx.annotation.Nullable;
    import androidx.fragment.app.Fragment;
    import androidx.lifecycle.Observer;
    import androidx.lifecycle.ViewModelProviders;


    import java.util.Objects;

    public class FragmentHome extends Fragment implements AddBookDialogue.AddBookDialogueListener, View.OnClickListener {

     private BookViewModel bookPass;
     public Book passedBook;
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
    View w = inflater.inflate(R.layout.fragment_home, container, false);
    Button b = (Button) w.findViewById(R.id.button);
    b.setOnClickListener(this);
    return w;
    }
        @Override
        public void onStart() {
            super.onStart();
        }

        @Override
        public void getTexts(String bookAuthor, String bookTitle, String bookBarcode, String bookCourse, String selectedBookshelf) {
                    Book a = new Book(bookAuthor, bookTitle, bookBarcode, bookCourse, selectedBookshelf);
                    Toast.makeText(getActivity(), a.toString(), Toast.LENGTH_LONG).show();
                    bookPass.setBook(a);

    }

        @Override
        public void onClick(View w) {
            if (w.getId() == R.id.button) {
                AddBookDialogue addBookDialogue = new AddBookDialogue();
                assert getFragmentManager() != null;
                addBookDialogue.setTargetFragment(FragmentHome.this, 1 );
                addBookDialogue.show(getFragmentManager(), "add book dialogue");

            }
        }

        @Override
        public void onActivityCreated(@Nullable Bundle savedInstanceState) {
            super.onActivityCreated(savedInstanceState);
            bookPass = ViewModelProviders.of(Objects.requireNonNull(getActivity())).get(BookViewModel.class);
            bookPass.getBook().observe(getViewLifecycleOwner(), new Observer<Book>() {
                @Override
                public void onChanged(@Nullable Book book) {
                     passedBook = book;
                }
            });
        }
    }

This is the receiving fragment

import android.os.Bundle;

import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProviders;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;


import java.util.ArrayList;
import java.util.Objects;

public class Fragment_Withdrawn extends Fragment {

private RecyclerView mRecyclerview;
private RecyclerView.LayoutManager myLayoutManager;
private RecyclerViewAdapter withDrawnAdapter;
public ArrayList<Book> bookSet;
private BookViewModel bookPass;
public Book passedBook;
    View withdrawnView;
    public Fragment_Withdrawn() {
    }

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        bookSet = new ArrayList<>();
        bookSet.add(passedBook);
        withdrawnView = inflater.inflate(R.layout.fragment_withdrawn, container, false);
        mRecyclerview = (RecyclerView) withdrawnView.findViewById(R.id.WithdrawnRecycler);
        myLayoutManager= new LinearLayoutManager(getActivity());
        Log.d("debugMode", "mayTest");
        mRecyclerview.setLayoutManager(myLayoutManager);
        withDrawnAdapter = new RecyclerViewAdapter(getContext(  ), bookSet);
        mRecyclerview.setAdapter(withDrawnAdapter);
        return withdrawnView;
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        bookPass = ViewModelProviders.of(Objects.requireNonNull(getActivity())).get(BookViewModel.class);
        bookPass.getBook().observe(getViewLifecycleOwner(), new Observer<Book>() {
            @Override
            public void onChanged(@Nullable Book book) {
                passedBook = book;
            }
        });
    }
}

And this is the recycleviewadapter

import android.content.Context;

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import java.util.ArrayList;

public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.MyViewHolder> {
    public ArrayList<Book> bookSet;
    public Context newContext;


    public static class MyViewHolder extends RecyclerView.ViewHolder{
        public TextView textView;
        public MyViewHolder(View vv) {
            super(vv);
            textView = vv.findViewById(R.id.RecyclerViewTextView);
        }
    }

    public RecyclerViewAdapter(Context newContext , ArrayList<Book> bookSet) {
        this.newContext = newContext; this.bookSet = bookSet;
    }

    @NonNull
    @Override
    public RecyclerViewAdapter.MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view;
        view = LayoutInflater.from(newContext).inflate(R.layout.recycler_view_text_view, parent, false);
        MyViewHolder viewHolder = new MyViewHolder(view);
        return viewHolder;

    }

    @Override
    public void onBindViewHolder(@NonNull RecyclerViewAdapter.MyViewHolder holder, int position) {
        if(!bookSet.isEmpty())
holder.textView.setText(bookSet.get(position).getTitle());
    }

    @Override
    public int getItemCount() {
        return bookSet.size();
    }
}

The ViewModel

   package com.example.newvibe;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;

public class BookViewModel extends ViewModel {

    private MutableLiveData<Book> book = new MutableLiveData<>();

    public void setBook(Book input){
        book.setValue(input);
    }

    public LiveData<Book> getBook(){
        return book;
    }
}

Finally, just in case, here is the AddDiaogue, but it succesfully passed data to the fragment in which it is called upon

package com.example.newvibe;

import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;

import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.Toast;

import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatDialogFragment;

public class AddBookDialogue extends AppCompatDialogFragment {
    private EditText editTextBookAuthor;
    private EditText editTextBookTitle;
    private EditText editTextBookCourse;
    private EditText editTextBookBarcode;
    private Spinner spinnerBookshelf;
    private AddBookDialogueListener listener;

    @Override
    public void onStart() {
        super.onStart();
        AlertDialog d = (AlertDialog)getDialog();
        if(d !=null){
            Button positiveButton = (Button) d.getButton(Dialog.BUTTON_POSITIVE);
            positiveButton.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    String bookAuthor =  editTextBookAuthor.getText().toString();
                    String bookTitle =  editTextBookTitle.getText().toString();
                    String bookBarcode =  editTextBookBarcode.getText().toString();
                    String bookCourse =  editTextBookCourse.getText().toString();
                    String selectedBookshelf =  spinnerBookshelf.getSelectedItem().toString();
if(bookAuthor.isEmpty() || bookTitle.isEmpty() || bookBarcode.isEmpty() || bookCourse.isEmpty() ){
    Toast toast = Toast.makeText(getActivity(), "You didn't enter all the neccesary data", Toast.LENGTH_SHORT);
    toast.setGravity(Gravity.CENTER| Gravity.CENTER_HORIZONTAL, 0, 0);
    toast.show();

}else{
    if(bookBarcode.length()!=8){
        Toast toas =Toast.makeText(getActivity(), "Barcodes should be eight digits long", Toast.LENGTH_LONG);
        toas.setGravity(Gravity.CENTER| Gravity.CENTER_HORIZONTAL, 0, 0 );
        toas.show();
    }else{
        listener.getTexts(bookAuthor, bookTitle,bookBarcode,bookCourse,selectedBookshelf );
        dismiss();
    }
}
                }
            });
        }
        }


    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
        LayoutInflater inflater = getActivity().getLayoutInflater();
        View view = inflater.inflate(R.layout.dialogue_addbook, null);
        builder.setView(view)
                .setNegativeButton("cancel", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialogInterface, int i) {

                    }
                })
                .setPositiveButton("Add", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialogInterface, int i) {

                    }
                });


        editTextBookAuthor = view.findViewById(R.id.author);
        editTextBookTitle= view.findViewById(R.id.bookTitle);
        editTextBookBarcode= view.findViewById(R.id.bookBarcode);
        editTextBookCourse= view.findViewById(R.id.bookCourse);
        spinnerBookshelf= view.findViewById(R.id.spinnerBookshelf);
        ArrayAdapter<CharSequence> spinnerAdapter = ArrayAdapter.createFromResource(getActivity(), R.array.addBookBookshelfsAvailable, android.R.layout.simple_spinner_item);
        spinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        spinnerBookshelf.setAdapter(spinnerAdapter);
        return builder.create();
    }

    public void onAttach(Context context) {
        super.onAttach(context);
        try {
            listener = (AddBookDialogueListener) getTargetFragment();
        } catch(ClassCastException e){
            throw new ClassCastException(context.toString() + "no listener");
        }
    }
    public interface AddBookDialogueListener{
        void getTexts(String bookAuthor, String bookTitle, String bookBarcode, String bookCourse, String selectedBookshelf);

    }

}

Help much appreciated :)

This is more of an Architecture question than a bug fixing one.

Let's assume this app lets users mark a Bus and/or Bus Stations as a favourite. My question is, should I have a ViewModel with both UseCases or should I build a UseCase that encapsulates the current logic?

Also for the question part, I'm not entirely sure the way I should expose the combined data to the UI layer (see favouritesExposedLiveData)

Thanks in advance any feedback is welcome, here's my ViewModel you can assume each UseCase is passing the correct data from the data source(s).

open class FavouritesViewModel @Inject internal constructor(
            private val getFavouriteStationsUseCase: GetFavouriteStationsUseCase,
            private val getFavouriteBusesUseCase: GetFavouriteBusesUseCase,
            private val favouriteMapper: FavouriteMapper,
            private val busMapper: BusMapper,
            private val stationMapper: StationMapper) : ViewModel() {

        private val favouriteBusesLiveData: MutableLiveData<Resource<List<BusView>>> = MutableLiveData()
        private val favouriteStationsLiveData: MutableLiveData<Resource<List<StationView>>> = MutableLiveData()

        private lateinit var favouritesMediatorLiveData: MediatorLiveData<List<FavouriteView>>
        private lateinit var favouritesExposedLiveData: LiveData<Resource<List<FavouriteView>>>

        init {
            fetchFavourites()
        }

        override fun onCleared() {
            getFavouriteStationsUseCase.dispose()
            getFavouriteBusesUseCase.dispose()
            super.onCleared()
        }

        fun getFavourites(): LiveData<Resource<List<FavouriteView>>> {
            return favouritesExposedLiveData
        }

        private fun fetchFavourites() {
            favouritesMediatorLiveData.addSource(favouriteStationsLiveData, { favouriteStationListResource ->
                if (favouriteStationListResource?.status == ResourceState.SUCCESS) {
                    favouriteStationListResource.data?.map {
                        favouriteMapper.mapFromView(it)
                    }
                }
            })

            favouritesMediatorLiveData.addSource(favouriteBusesLiveData, { favouriteBusesListResource ->
                if (favouriteBusesListResource?.status == ResourceState.SUCCESS) {
                    favouriteBusesListResource.data?.map {
                        favouriteMapper.mapFromView(it)
                    }
                }
            })

            getFavouriteStationsUseCase.execute(FavouriteStationsSubscriber())
            getFavouriteBusesUseCase.execute(FavouriteBusesSubscriber())
        }

        inner class FavouriteStationsSubscriber : DisposableSubscriber<List<Station>>() {
            override fun onComplete() {}

            override fun onNext(t: List<Station>) {
                favouriteStationsLiveData.postValue(Resource(ResourceState.SUCCESS, t.map { stationMapper.mapToView(it) }, null))
            }

            override fun onError(exception: Throwable) {
                favouriteStationsLiveData.postValue(Resource(ResourceState.ERROR, null, exception.message))
            }

        }

        inner class FavouriteBusesSubscriber : DisposableSubscriber<List<Bus>>() {
            override fun onComplete() {}

            override fun onNext(t: List<Bus>) {
                favouriteBusesLiveData.postValue(Resource(ResourceState.SUCCESS, t.map { busMapper.mapToView(it) }, null))
            }

            override fun onError(exception: Throwable) {
                favouriteBusesLiveData.postValue(Resource(ResourceState.ERROR, null, exception.message))
            }

        }
    }

Note: Currently the MediatorLiveData (favouritesMediatorLiveData)is not binding the data back to the favouritesExposedLiveData since at this time, I'm not sure this is the correct way to go ;).

Trying to understand what is the difference with using the ViewModel to keep some of the state of the activity or fragment, and saving them with the savedInstanceState bundle.

Got a impression that the ViewModel instance is kept alive when the activity/fragment is destroyed by os in the case like configuration change so that when os recreate the activity/fragment could get the data from the ViewModel instance which is still valid.

Does it apply to minimize the app and re-open it?

Did some test, seems minimize the app and re-open the app, the os will recreate the activity/fragment with the stavedInstanceState bundle in the onCreate() not null (whatever is saved when the onSaveInstanceStae() is called). But the ViewModel has been cleared so a new instance is created without previous ones data.

Does it it mean although is in this case the os can retrieve the saved instance state and pass to activity/fragment's onCreate(), but the ViewModel has to be a new instance without previous instance's data, or the viewModel needs do to some extra step inorder to store/restore the data cross the instances?

I'm facing a problem with display data in fragmentRecycler I'm using retrofit to get json using viewModel and liveData to observe and survive from activity configurations but i don't know how to display these respondes as item's in my Fragment recyclerView this is my fragment onCreate Method code :-

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

    voucherViewModel model = ViewModelProviders.of(getActivity()).get(voucherViewModel.class);
    model.getVoucher().observe(getActivity(), new Observer<List<Voucher>>() {
        @Override
        public void onChanged(@Nullable List<Voucher> vouchers) {
            Log.e("on change", "onChanged: " );
            adapter = new homeRecycleViewAdapter(getContext(),vouchers );
            recyclerView.setLayoutManager(new LinearLayoutManager(context));
            recyclerView.setAdapter(adapter);
        }

    });

}//end onCreate

my OnCreateView Method Code :-

public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.home_fragment_layout, container, false);
    adapter = new homeRecycleViewAdapter(context);
    recyclerView = view.findViewById(R.id.homeRecyclerViewID);

    recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
    recyclerView.setAdapter(adapter);
    //return inflated view
    return view;
}

so i hope to get help :)

Similar Question 4 (1 solutions) : ViewModel in fragment clears values on screen rotation

Similar Question 6 (2 solutions) : Shared ViewModel's onCleared() is never called

Similar Question 7 (2 solutions) : How does one pass arguments to a fragment?

cc