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;
}
}
}
CodePudding user response:
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();
}