How do I retrieve data independently from nested JSON objects that are part of the same group in android/java? ( more clarity/details in body)
Asked Answered
U

1

0

I have a pretty common scenario, but I am stuck trying to figure out how to retrieve data from certain json objects within the same json response but returning different size of items. To make it less confusing, here's my json response:

{
    "totalResults": 5,
    "meetings": [
        {
            "id": "754e6670-2376-4eb2-b5f6-ea63ca7c2669",
            "dateStart": "2019-11-28T15:25:36.000Z",
            "dateEnd": "2019-11-28T23:00:36.000Z",
            "visitors": [
                {
                    "visitId": "34608af6-bbe4-439c-b472-500790385f60",
                    "firstName": "Lord",
                    "lastName": "Wilkins",
                    "email": "  [email protected]",
                },
                {
                    "visitId": "fe61e1f0-34f1-4701-b806-45395980acfb",
                    "firstName": "cg",
                    "lastName": "cg",
                    "email": "[email protected]",
                },
                {
                    "visitId": "226eed33-b2ca-4406-b085-8534c2f87c69",
                    "firstName": "ar",
                    "lastName": "fg",
                    "email": "[email protected]",
                }
            ]
        },
        {
            "id": "cf6934c1-3800-4b79-9d0f-570934097d26",
            "dateStart": "2019-11-30T00:00:06.000Z",
            "dateEnd": "2019-11-30T02:00:06.000Z",
            "visitors": [
                {
                    "visitId": "03a01b91-f307-4a04-ae7a-71a5e3e183c5",
                    "firstName": "ar",
                    "lastName": "fg",
                    "email": "[email protected]",
                }
            ]
        },
        {
            "id": "a46130c3-5b80-419e-8c57-e17428d4b735",
            "dateStart": "2019-11-28T13:00:09.000Z",
            "dateEnd": "2019-11-29T02:45:09.000Z",
            "visitors": [
                {
                    "visitId": "5068a774-1cf3-45e2-af65-b98ab4dfbff2",
                    "firstName": "dot",
                    "lastName": "mn",
                    "email": "[email protected]",
                }
            ]
        }
    ]
}

If anyone's interested here's my model class: https://pastebin.com/fUmiBiQP

Now, I am trying to retrieve the date start and date end and place it on top of first and last name and email, something like this :

" Nov 28, 2:00pm - 6:00 pm 
  lord wilkins
  [email protected]

  Nov 28, 2:00pm - 6:00 pm 
  cg cg
  [email protected]"

Notice how I repeat the same date that applies to entries grouped together , I want to achieve that, but currently I just am able to retrieve first and last name and email, but not the date based on how I have arranged my code. Everytime I try to modify the code to include meetings start and end date it crashes with either an index out of bounds exception due to size or null pointer exception.The issue happens in adapter. Here's my adapter code for reference:

public class UpcomingGuestListAdapter extends RecyclerView.Adapter<BaseViewHolder>  {

    private Context context;
    private UpcomingGuestListAdapter.Callback mCallback;
    private List<UpcomingGuestsList.Visitor> mUpcomingGuestListResponseList;
    private List<UpcomingGuestsList.Meeting> mUpcomingGuestListResponseMeetingList;

    public class MyViewHolder extends BaseViewHolder {
        public TextView name, email, arrivalDate;


        public MyViewHolder(@NonNull View itemView) {
            super(itemView);
            Timber.d("viewholder butterknife bind");
            name = itemView.findViewById(R.id.upcoming_guest_name);
            email = itemView.findViewById(R.id.upcoming_guest_email);
            arrivalDate = itemView.findViewById(R.id.arrival_date);
            ButterKnife.bind(this, itemView);
        }

        @Override
        protected void clear() {

        }
        public void onBind(int position) {
            super.onBind(position);
            final UpcomingGuestsList.Visitor guest = mUpcomingGuestListResponseList.get(position);
            final UpcomingGuestsList.Meeting meeting = mUpcomingGuestListResponseMeetingList.get(position);

            String guestWholeName = guest.firstName + " "+ guest.lastName;
            if( guest.getFirstName() != null && guest.getLastName() != null) {
                name.setText(guestWholeName);
            }



            if (guest.getEmail() != null) {
                email.setText(guest.getEmail());
            } else {
                email.setText("");
            }

            String meetingDate = meeting.dateStart + "-"+ meeting.dateEnd;
            if( meeting.getDateStart() != null && meeting.getDateEnd() != null) {
                arrivalDate.setText(meetingDate);
            }
            itemView.setOnClickListener(v -> {
                if(mCallback != null) {
                    mCallback.onItemClick( guest.getFirstName(), guest.getLastName(), guest.getEmail());
                }
            });

        }

    }

    @NonNull
    @Override
    public BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View itemView = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.item_upcoming_guest, parent, false);

        return new UpcomingGuestListAdapter.MyViewHolder(itemView);
    }

    public interface Callback {
        void onItemClick(String guestFirstName, String guestLastName,String guestEmail);

    }

    @Override
    public long getItemId(int position) {
        Timber.d("item id for eventlist adapter : %s",position);
        return position;
    }

    public void addItems(@NonNull List<UpcomingGuestsList.Visitor> repoList) {

        mUpcomingGuestListResponseList.addAll(repoList);
         notifyDataSetChanged();
    }

    public void addMeetingItems(@NonNull List<UpcomingGuestsList.Meeting> meetingList) {
        mUpcomingGuestListResponseMeetingList.addAll(meetingList);
        notifyDataSetChanged();
    }
    @Override
    public void onBindViewHolder(BaseViewHolder holder, final int position) {
        holder.onBind(position);

    }

    public UpcomingGuestListAdapter(Context context, List<UpcomingGuestsList.Visitor> visitorGuestList, List<UpcomingGuestsList.Meeting> meetingList) {
        Timber.d("SearchGuestListAdapter constructor");
        this.context = context;
        this.mUpcomingGuestListResponseList = visitorGuestList;
        this.mUpcomingGuestListResponseMeetingList = meetingList;


    }

    @Override
    public int getItemCount() {

        return (mUpcomingGuestListResponseList  != null)?mUpcomingGuestListResponseList.size():0;
    }

    public void setCallback(UpcomingGuestListAdapter.Callback callback) {
        mCallback = callback;
    }

}

Here's the code in activity where it gets the data from :

   @Override
    public void updateUpcomingGuestList(List<UpcomingGuestsList.Visitor> guestList,List<UpcomingGuestsList.Meeting> meetingList) {

        mUpcomingGuestAdapter.addItems(guestList);
        mUpcomingGuestAdapter.addMeetingItems(meetingList);
        mUpcomingGuestAdapter.notifyDataSetChanged();
    }

In the same activity this is how I set my adapter on create :

   guestList = new ArrayList<>();
        mUpcomingGuestAdapter = new UpcomingGuestListAdapter(this, guestList, meetingList);
        mUpcomingGuestAdapter.setCallback(this);
        mLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
        mRecyclerView.setLayoutManager(mLayoutManager);
        mRecyclerView.setItemAnimator(new DefaultItemAnimator());
        mRecyclerView.setAdapter(mUpcomingGuestAdapter);
        mPresenter.onViewPrepared();

and the code for on viewprepared where i retrieve the json data :

 @Override
    public void onViewPrepared() {
        getCompositeDisposable().add(getDataManager()
                .getUpcomingGuestListApiCall(AppPreferencesHelper.getInstance().getCurrentUserId())
                .subscribeOn(getSchedulerProvider().io())
                .observeOn(getSchedulerProvider().ui())
                .subscribe(response -> {
                    int sizercount = 0;
                    for (int i = 0; i <response.totalResults; i++) {
                            Objects.requireNonNull(getMvpView()).updateUpcomingGuestList(response.meetings.get(i).getVisitors(),response.getMeetings());

                            sizercount++;
                    }

                    totalGuests = sizercount;

                    Timber.d("total results value is : from upcomingguests %s",response.totalResults);
                    getDataManager().getUpcomingGuestsList();
                    AppPreferencesHelper.getInstance().setTotalUpcomingGuestCount(sizercount);

                }, throwable -> {
                    if (!isViewAttached()) {
                        return;
                    }

                    // handle the error here

                    if (throwable instanceof ANError) {
                        ANError anError = (ANError) throwable;
                        Timber.d("it is ERROR in displaylocation in Registervisitorpresenter:%s", anError.getErrorDetail());

                        handleApiError(anError);
                    }
                }));
    }

As you can see from above, I try to retrieve the visitors list and meeting list together here: Objects.requireNonNull(getMvpView()).updateUpcomingGuestList(response.meetings.get(i).getVisitors(),response.getMeetings()); , but not sure if its the right way to do it as both visitors list and meetings list have different positions/size based on the data and this is probably causing the crash.Is there a way to go around this and update my code so I can assign the correct dates to the right visitors so I can achieve the output I listed above? Any help appreciated.

Thanks! Happy to share more info if needed.

Urgency answered 27/11, 2019 at 23:11 Comment(4)
You have a list of meetings, each containing a list of visitors. The list size of meetings is independent of the size of visitors. You calculate the item count based on only one list, hence the ArrayIndexOutOfBoundsException. It would seem for this dataset - a list of lists - you require a nested recycler view for each meeting.Ole
mark keen, thanks for your response! Can you explain with respect to my code, would be more useful to see how I can modify it. not too familiar with nested recycler view.Urgency
Also, I am not sure a nested recycler view solve my problem, as it seems like ( based on the ones i looked online), it fixes only UI look and feel, I just want to manipulate the data so it all looks the same, only the visitors with the same meeting time have the meeting time applied to them in a repeated fashion, unlike the visitors with only 1 meeting time.Urgency
Ok I understand now. Create a pojo that reflects the end data type and use a map function in your rxchain to produce the desired single list dataset - manipulate the data to produce the correct dataset is more pragmatic and readable.Ole
M
0

Instead of trying to use response as it came from your API(which is causing problems), you could declare a model class that will be more friendly and perform some operations on your API response to create a list of your new model class.

Declare a new model UpcomingGuest that takes a Visitor object and a Meeting in its constructor.

public class UpcomingGuest {


    private String visitId;

    private String firstName;

    private String lastName;

    private String email;

    private Object phone;

    private String status;

    private String meetingId;

    private String meetingDateStart;

    private String meetingDateEnd;

    public UpcomingGuest(UpcomingGuestsList.Visitor visitor, UpcomingGuestsList.Meeting meeting){

        this.visitId = visitor.getVisitId();
        this.firstName = visitor.getFirstName();
        this.lastName = visitor.getLastName();
        this.email = visitor.getEmail();
        this.phone = visitor.getPhone();
        this.status = visitor.getStatus();
        this.meetingId = meeting.getId();
        this.meetingDateStart = meeting.getDateStart();
        this.meetingDateEnd = meeting.getDateEnd();

    }

    public String getVisitId() {
        return visitId;
    }

    public void setVisitId(String visitId) {
        this.visitId = visitId;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public Object getPhone() {
        return phone;
    }

    public void setPhone(Object phone) {
        this.phone = phone;
    }

    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }

    public String getMeetingId() {
        return meetingId;
    }

    public void setMeetingId(String meetingId) {
        this.meetingId = meetingId;
    }

    public String getMeetingDateStart() {
        return meetingDateStart;
    }

    public void setMeetingDateStart(String meetingDateStart) {
        this.meetingDateStart = meetingDateStart;
    }

    public String getMeetingDateEnd() {
        return meetingDateEnd;
    }

    public void setMeetingDateEnd(String meetingDateEnd) {
        this.meetingDateEnd = meetingDateEnd;
    }


}

Then construct a list of UpcomingGuest from your response which will be used in your RecyclerView's adapter.

.subscribe(response -> {
                    int sizercount = 0;
                    for (UpcomingGuestsList.Meeting meeting: response.meetings) {
                        for(UpcomingGuestsList.Visitor visitor: meeting.visitors){
                            upcomingGuests.add(new UpcomingGuest(visitor,meeting));
                        }
                    }

                    // send upcomingGuests results to view


                }

Then in your RecyclerView's adapter use a List of UpcomingGuest instead of the two Lists you are using.

private List<UpcomingGuest> mUpcomingGuestList;

EDIT

For your sorting issue, you could sort your response before iterating through them like below:

.subscribe(response -> {
        ArrayList<UpcomingGuest> upcomingGuests = new ArrayList<>();

        //sort meetings by date
        List<UpcomingGuestsList.Meeting> meetings = response.meetings;
        Collections.sort(meetings, new Comparator<UpcomingGuestsList.Meeting>() {
            @Override
            public int compare(UpcomingGuestsList.Meeting meeting, UpcomingGuestsList.Meeting t1) {
                return meeting.getDateStart().compareTo(t1.getDateStart());
            }
        });

        for (UpcomingGuestsList.Meeting meeting: meetings) {
            //sort visitors by last name
            List<UpcomingGuestsList.Visitor> visitors = meeting.visitors;
            Collections.sort(visitors, new Comparator<UpcomingGuestsList.Visitor>() {
                @Override
                public int compare(UpcomingGuestsList.Visitor visitor, UpcomingGuestsList.Visitor t1) {
                    return visitor.getLastName().compareTo(t1.getLastName());
                }
            });

            for(UpcomingGuestsList.Visitor visitor: visitors){

                upcomingGuests.add(new UpcomingGuest(visitor,meeting));
            }
        }

        // send upcomingGuests results to view


}
Mozart answered 28/11, 2019 at 8:23 Comment(27)
Thanks so much!!! It actually works like a charm and reduces lines of code too! I really appreciate your help! Just one extra thing I was looking for was, How do I get a sorted list back (based on the dates) instead of the random order I am getting right now. I want to have a result showing the sorted list from nearest date to the furthest. Also, if there are 3 visitors grouped within the same date I want to sort them alphabetically. So basically want to sort twice, once for dates and after dates alphabetically for dates with more than 1 entries. Any idea how best to go about it?Urgency
I have edited the answer to address your sorting issueMozart
Networks sorry to bug you again , but was wondering if its possible to achieve this by modifying my code somehow ,like with least possible modifications and supporting sticky headers ? mithunkumarc.blogspot.com/2016/09/… , just want to group the cards by date as shown in the blog.Something even better would be grouping them by date and time.any idea? really appreciate it!Urgency
let's continue this here chat.stackoverflow.com/rooms/203741/…Mozart
just saw you removed the link in the chat, not sire why. It works like a charm and exactly what I expect! Thanks so much for doing it! Was wondering why you removed it, is it still not complete or has issues?Urgency
also, extremely impressed how you managed to wrap this in a day. you're a geniusUrgency
As promised i opened a bounty on this to reward you. Will assign it within 24 hours or whenever stackoverflow lets me :)Urgency
I came across an issue with the empty view holder and no internet connection where it crashes with a null pointer exception : java.lang.NullPointerException: Attempt to invoke interface method 'int java.util.List.size()' on a null object reference at com.saber.stickyheader.stickyView.StickHeaderRecyclerView.getItemCount do you know why this could be happening? can share my new code if that helpsUrgency
easiest way to reproduce this is to comment out the data code in your sample : when the data returns 0 guestsUrgency
Ok, I will checkout what's causing that and get back to you.Mozart
Thank you! I had the network connection and empty view screen showing before adding sticky headers. Wonder if its something in the adapter or activity thats not routing to the empty view as expected.Urgency
Thanks! I appreciate it! Everything works great now!Urgency
Thanks so much! One last favor to ask ( I promise) , how do I modify your test project if the json response were simpler like this : pastebin.com/1uR5FThr instead of the one in the question here. With almost the same conditions with the header showing start date and cards grouped together based on the same day( sort of like this: pasteboard.co/IKG0vkG.png). Sorry to extend this question for so long, but I promise this is the last favor I need for this question. thanks so much for helping so far, you're amazing!Urgency
Happy New Year Ephraim! New year and new question : #59592068 , The sample Open source project is included in there, just need tiny help displaying the time and dates correctly, as per the first screenshot, whenever you get a chance, if you can help, that'll be a great help! I am hoping it wont be too complex.Urgency
Many Happy returns. Will take a look at itMozart
Thanks! Let me know if you need any info or details (if the question is confusing), happy to share.Urgency
Yeah the question was a little bit confusing but I think I understand the basic idea on what you are trying to achieve. I have pushed changes to the StickyHeader github repo.Mozart
Thanks for updating it! However, is it possible to just show the events for that specific selected date and none other. Currently, if I select dec 9, i still see dec 12 and other events and it doesn't update correctly. when I select dec 25, which has no events, it still shows dec 24 and dec 30 and so on. Is it possible to just show only the event for the selected day and nothing else for that repo?Urgency
Also feel free to use the sample project in my question I referenced in the link above. It has everything setup, only needs the selected days to show the respective events perspective to the day.(rest all events should disappear)Urgency
Ok will try to push changes when I'm less busyMozart
no problem! sorry to bug you with this. take your time :)Urgency
I have pushed new changes. Changes are located in calendar package.Mozart
Thanks so much. That works! Do you want to post your link as an answer to the question so I can reward you the correct answer. Once again thanks for taking time for this. Also, wondering will it be difficult to apply this calendar logic to your sticky headers sample project you created, with the same changes where only events respective to the day show up? That is exactly what I am trying to achieve as an end result at the end.Urgency
Also, this is what I aim to achieve in the long run,so your sample project will help a lot! pasteboard.co/IOKFQeY.pngUrgency
It shouldn't be difficult to apply same logic to sticky header sample. But I was wondering since reservations for one day is shown at a time the sticky header is not really needed. You could just use a normal textview with a recycler below it.Mozart
that makes sense. Thanks so much, can you paste the link to your repo as an answer to my question, gotta reward you +25 for helping out :)Urgency
Let us continue this discussion in chat.Urgency

© 2022 - 2024 — McMap. All rights reserved.