Android recyclerview does not show any data
Asked Answered
D

1

0

I'm using a template and I replaced the placeholder data with a Retrofit 2 request that parses a JSON file of the data from an URL and puts it into an object list. I ran into a problem where my retrofit call gets executed and returns items = 0 at first, then the code executes further ahead using the value items=0 and after quite a few steps in the debugger, it comes back to the call and shows the correct response (items=12) but the Adapter/RecycleView operations seem to have already been executed and they do not use the late-arrived parsed data.

MainActivity.java

public class MainActivity extends AppCompatActivity {
private View parent_view;
List<Task> items = new List<Task>() {...}
private RecyclerView recyclerView;
private AdapterTasks mAdapter;
private int animation_type = ItemAnimation.BOTTOM_UP;


@Override
protected void onCreate(Bundle savedInstanceState) {
    items = DataGenerator.getTasksData();
    items.addAll(DataGenerator.getTasksData());
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_list_animation);
    parent_view = findViewById(android.R.id.content);
    initComponent();

}
private void initComponent() {

    recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
    recyclerView.setLayoutManager(new LinearLayoutManager(this));
    recyclerView.setHasFixedSize(true);
    animation_type = ItemAnimation.FADE_IN;
    setAdapter();
}

private void setAdapter() {
    //set data and list adapter
    mAdapter = new AdapterTasks(this, items, animation_type);
    recyclerView.setAdapter(mAdapter);

    // on item list clicked
    mAdapter.setOnItemClickListener(new AdapterTasks.OnItemClickListener() {
        @Override
        public void onItemClick(View view, Task obj, int position) {
            //open task view
        }
    });
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    getMenuInflater().inflate(R.menu.menu_list_animation, menu);
    return true;
}

Retrofit request

public static List<Task> getTasksData() {
    HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
    interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
    OkHttpClient client = new OkHttpClient.Builder().addInterceptor(interceptor).build();

    Gson gson = new GsonBuilder()
            .setDateFormat("yyyy-MM-dd HH:mm:ss")
            .create();

    Retrofit retrofit= new Retrofit.Builder()
            .baseUrl("http://10.0.2.2/index.php/")
            .addConverterFactory(GsonConverterFactory.create(gson))
            .client(client)
            .build();

    TasksApi taskAPI =retrofit.create(TasksApi.class);
    Call<List<Task>> call = taskAPI.getAllTasks();

    call.enqueue(new Callback<List<Task>>() {
        @Override
        public void onResponse(Call<List<Task>> call, Response<List<Task>> response) {
            items = response.body();
        }
        @Override
        public void onFailure(Call<List<Task>> call, Throwable t) {
        }
    });
    return items;
}

Adapter

public class AdapterTasks extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

private List<Task> items;
private Context ctx;

private OnItemClickListener mOnItemClickListener;
private int animation_type = 0;

public interface OnItemClickListener {
    void onItemClick(View view, Task obj, int position);
}

public void setOnItemClickListener(final OnItemClickListener mItemClickListener) {
    this.mOnItemClickListener = mItemClickListener;
}

public AdapterTasks(Context context, List<Task> items, int animation_type) {
    this.items = DataGenerator.getTasksData();
    this.items.addAll(DataGenerator.getTasksData());
    ctx = context;
    this.animation_type = animation_type;
}

public class OriginalViewHolder extends RecyclerView.ViewHolder {
    public View lyt_parent;
    public TextView laikas;
    public TextView klientas;
    public TextView darbas;
    public TextView komentaras;
    public Button atlikta;


    public OriginalViewHolder(View v) {
        super(v);
        laikas = (TextView) v.findViewById(R.id.laikas);
        klientas = (TextView) v.findViewById(R.id.name);
        lyt_parent = (View) v.findViewById(R.id.lyt_parent);
        darbas = (TextView) v.findViewById(R.id.darbas);
        komentaras = (TextView) v.findViewById(R.id.komentaras);
        atlikta = (Button) v.findViewById(R.id.atlikta);

    }
}

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    RecyclerView.ViewHolder vh;
    View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_task, parent, false);
    vh = new OriginalViewHolder(v);
    return vh;
}

// Replace the contents of a view (invoked by the layout manager)
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {
    Log.e("onBindViewHolder", "onBindViewHolder : " + position);
    if (holder instanceof OriginalViewHolder) {
        OriginalViewHolder view = (OriginalViewHolder) holder;

        Task t = items.get(position);
        view.laikas.setText((CharSequence) t.AtlikData);
        view.klientas.setText(t.KlientasID);
        view.darbas.setText(t.VeiksmoID);
        view.komentaras.setText(t.Komentaras);
        view.lyt_parent.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (mOnItemClickListener != null) {
                    mOnItemClickListener.onItemClick(view, items.get(position), position);
                }
            }
        });
        setAnimation(view.itemView, position);
    }
}

@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
    recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
        @Override
        public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
            on_attach = false;
            super.onScrollStateChanged(recyclerView, newState);
        }
    });
    super.onAttachedToRecyclerView(recyclerView);
}

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

private int lastPosition = -1;
private boolean on_attach = true;

private void setAnimation(View view, int position) {
    if (position > lastPosition) {
        ItemAnimation.animate(view, on_attach ? position : -1, animation_type);
        lastPosition = position;
    }
}

}

Dander answered 22/5, 2022 at 1:4 Comment(7)
@TylerV thank you, I've read the post and I suspected that it may be the case but it is not clear to me how I can reorganize the flow of my function, the example provided didn't help as I'm not familiar with kotlinDander
@TylerV I'm sorry, I still find it difficult to understand. Tyler, could you explain it in a more beginner friendly way, please? Should I just put the setAdapter() in the updateDisplay() method? And what do you mean by "define a callback to pass to it"?Dander
I added an answer with some more detail - it was getting too long for a comment.Cockle
@TylerV words cannot express my gratitude, your help came at a crucial time as my deadline is very soon, thank you for what you are doingDander
@TylerV I've made your suggestion work perfectly but now I've come to a new problem where I have to access multiple DB tables at the same time, should I use nested calls, or is there a way to stack multiple requests in a single call?Dander
I would recommend you ask a new question with some example code showing what exactly you are trying to do. If the subsequent calls depend on the initial ones nested is probably what you'd need.Cockle
@TylerV thank you, I'll try to use nested for now, just wanted to know if there is a function for multiple calls that I was not aware of, if I run into problems with the implementation which I can't solve despite researching it on my own, I'll post a new question!Dander
C
0

Your method is asynchronous, so the code in onResponse runs in the future, after your getTaskData method has already returned an empty list. You would need to modify your program flow to accommodate this.

Here is an example of how you could define your own custom callback to handle that, by setting up the RecyclerView empty at first and then adding the data once it arrives.

Step 1: define an interface somewhere (e.g. in the activity or retrofit request class)

interface OnTasksRetrieved {
    void getResult(List<Task> result);
}

Step 2: change your getTasksData method to take a callback implementing that interface instead of returning a list

public static void getTasksData(OnTasksRetrieved callback) {
    //...
    call.enqueue(new Callback<List<Task>>() {
        @Override
        public void onResponse(Call<List<Task>> call, Response<List<Task>> response) {
            // Call your callback once you have retrieved the data
            callback.getResult(response.body());
        }
        @Override
        public void onFailure(Call<List<Task>> call, Throwable t) {
        }
    });

    // do not return anything
}

Step 3: when you start the async call in onCreate, create an instance of the callback interface to pass to it to handle the result

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_list_animation);
    parent_view = findViewById(android.R.id.content);
    initComponent();
    
    // start the async fetch here - it won't finish until
    // later in the future, when it will call the callback code
    // below to update the RecyclerView data
    getTasksData(
        new OnTasksRetrieved() {
            @Override
            public void getResult(List<Task> result) {
                // The code in here runs much later in the future - the adapter
                // will already have been set up, but will be empty.
                myAdapter.setItems(result); // add this method to the adapter
            }
        }
    );
}

You will need to modify the adapter so it doesn't take the List as an input, but instead has a setItems method something like this, which will be called once the data is retrieved:

private List<Task> items = new ArrayList<>();

public AdapterTasks(Context context,  int animation_type) {
    ctx = context;
    this.animation_type = animation_type;
}

public void setItems(List<Task> data) {
    items.clear();
    items.addAll(data);
    notifyDataSetChanged();
}
Cockle answered 22/5, 2022 at 1:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.