Api calls from a fragment using Volley - android

I am making API calls from my fragment and getting the response using volley. The API calls are made again every time I click on that fragment tab. I want the API call to take place only once. Is there any way to achieve this? I tried searching for the solution but did not find anything useful. Below is the code of my fragment.
public class Tab3News extends Fragment {
private RecyclerView newsView;
private NewsAdapter newsadapter;
String myxmlResponse;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
Log.d("Making request again","hello");
View layout = inflater.inflate(R.layout.tab3news, container, false);
newsView = (RecyclerView) layout.findViewById(R.id.newstable);
String symPassed = ((SendString)getActivity()).message;
String XmlURL = "http://demoapplication-env.us-east-2.elasticbeanstalk.com/?symbol="+symPassed+"&indicator=XML";
RequestQueue queue = Volley.newRequestQueue(getActivity().getApplicationContext());
StringRequest req = new StringRequest(Request.Method.GET, XmlURL,
new Response.Listener<String>()
{
#Override
public void onResponse(String response) {
try {
//processData(response);
myxmlResponse = response;
newsView.setHasFixedSize(true);
//newsView.setItemAnimator(new DefaultItemAnimator());
newsView.setLayoutManager(new LinearLayoutManager(getActivity()));
newsView.addItemDecoration(new DividerItemDecoration(getActivity(), DividerItemDecoration.VERTICAL_LIST));
newsadapter = new NewsAdapter(getActivity(),getData());
newsView.setAdapter(newsadapter);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (XmlPullParserException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
},
new Response.ErrorListener()
{
#Override
public void onErrorResponse(VolleyError error) {
// handle error response
}
}
);
queue.add(req);
return layout;
}
}

There are two ways to do this-
1- Call the API from the parent activity of this fragment and accordingly pass the data to the fragment using 'setArguments(bundle)', by doing this, your API would not get called everytime on loading of the fragment.
2- Keep a boolean value in the Preference whenever the API is called-
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
mMyPrefs = PreferenceManager.getDefaultSharedPreferences(this);
boolean isFirstTime = mMyPrefs.getBoolean("IS_FIRST_TIME", true);
if (mIsFirstTime)
{
SharedPreferences.Editor editPrefs = mMyPrefs.edit();
editPrefs.putBoolean("IS_FIRST_TIME", false);
editPrefs.apply();
callAPI();
}
else
{
//TO DO YOUR STUFF
}
}

I think it is better to call the api in activity and pass the arguments through bundle rather than using a flag in shared preference. Then again, its my personal opinion.

Related

Update recycler view in fragment from activity

I have a FloatingActionButton and RecyclerView in one of my fragments. Fab opens a new activity where user can save a task into sqlite and all the saved tasks from sqlite are shown in the recycler view. Now what I want is that when the user saves a new task and click on the back button of the activity from toolbar, the recycler view should be updated automatically. Right now, I have to switch to another fragment and then come back to the previous one to see the newly created task. I researched about it and found that interfaces are the best option for this but I am having problems passing the context of the fragment to the activity.
Here is the activity for new task creation:
public class AddTaskActivity extends AppCompatActivity {
DataUpdateListener dataUpdateListener;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_add_task);
dataUpdateListener = (CalendarFragment) getSupportFragmentManager().findFragmentById(R.id.navigation_calendar);
ActionBar supportActionBar = getSupportActionBar();
if (supportActionBar != null) {
supportActionBar.setTitle(R.string.add_task);
supportActionBar.setDisplayHomeAsUpEnabled(true);
}
}
private void saveTask(String task_type, String task) {
// this method is used to save the task in sqlite
byte[] imageByteArray;
if (addPictureBtn.getVisibility() == View.GONE) {
imageByteArray = Utils.getImageByteArray(selectedImage);
if (Utils.saveTask(task_type, imageByteArray, task, 0) != -1) {
AlertDialog alertDialog = Utils.showProgressDialog(this, R.layout.success_popup);
Button okBtn = (Button) alertDialog.findViewById(R.id.okBtn);
okBtn.setOnClickListener(v -> {
alertDialog.dismiss();
finish();
});
}
dataUpdateListener.onDataUpdate();
}
}
public interface DataUpdateListener {
void onDataUpdate();
}
}
This is my fragment which is implementing the interface:
public class CalendarFragment extends Fragment implements AddTaskActivity.DataUpdateListener {
CalendarView calendarView;
TextView noTaskFoundTV;
RecyclerView recyclerView;
FloatingActionButton addTaskBtn;
private FragmentCalendarBinding binding;
CalendarTasksAdapter calendarTasksAdapter;
public View onCreateView(#NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
binding = FragmentCalendarBinding.inflate(inflater, container, false);
return binding.getRoot();
}
#Override
public void onViewCreated(#NonNull #NotNull View view, #Nullable #org.jetbrains.annotations.Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
calendarView = view.findViewById(R.id.calendar);
Calendar calendar = Calendar.getInstance();
long milliTime = calendar.getTimeInMillis();
calendarView.setDate(milliTime, true, true);
recyclerView = view.findViewById(R.id.rv);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
recyclerView.setHasFixedSize(false);
noTaskFoundTV = view.findViewById(R.id.noTaskFound);
addTaskBtn = view.findViewById(R.id.fab);
addTaskBtn.setOnClickListener(v -> {
Intent intent = new Intent(getContext(), AddTaskActivity.class);
startActivity(intent);
});
fetchTodayPendingTasks();
}
public void fetchTodayPendingTasks() {
JSONObject todayTasksFromDB = Utils.getTodayPendingTasksFromDB();
if (todayTasksFromDB != null) {
noTaskFoundTV.setVisibility(View.GONE);
recyclerView.setVisibility(View.VISIBLE);
try {
JSONArray tasks = todayTasksFromDB.getJSONArray("tasks");
calendarTasksAdapter = new CalendarTasksAdapter(getActivity(), tasks);
recyclerView.setAdapter(calendarTasksAdapter);
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
#Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
}
#Override
public void onDataUpdate() {
//this toast never triggers/shown when the task is created from the activity
Toast.makeText(getContext(), "Triggered", Toast.LENGTH_SHORT).show();
}
}
For this kind of usage, the best practice is to use Room database which is basically wrapping sqlite with abstraction layer. And then you could use LiveData.
Perfect example with source code can be found here.
Please try to open activity through startActivityResult like
In fragment
Intent intent = new Intent(getContext(), AddTaskActivity.class); startActivityForResult(intent,requestcode);
In addtaskactivity
Intent inten =new Intent()
setResult with OK
and then again check onActivityResult in fragment with request code, you can refresh you view here
Or another way to check and refresh in onStart() method of fragment with one static Boolean variable updated from task activity and again false this Boolean from onstart when you finish refreshing. But first of all I would prefer first way.
You should use onResumed method of fragment lifecycle.
you should override onResumed Method on CalendarFragment
This method is called after returning to the main page.
call fetchTodayPendingTasks(); in onResumed method.
It is better to make changes in the fetchTodayPendingTasks. like this:
public class CalendarFragment extends Fragment implements AddTaskActivity.DataUpdateListener {
CalendarView calendarView;
TextView noTaskFoundTV;
RecyclerView recyclerView;
FloatingActionButton addTaskBtn;
private FragmentCalendarBinding binding;
CalendarTasksAdapter calendarTasksAdapter;
public View onCreateView(#NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
binding = FragmentCalendarBinding.inflate(inflater, container, false);
return binding.getRoot();
}
#Override
public void onViewCreated(#NonNull #NotNull View view, #Nullable #org.jetbrains.annotations.Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
calendarView = view.findViewById(R.id.calendar);
Calendar calendar = Calendar.getInstance();
long milliTime = calendar.getTimeInMillis();
calendarView.setDate(milliTime, true, true);
recyclerView = view.findViewById(R.id.rv);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
recyclerView.setHasFixedSize(false);
noTaskFoundTV = view.findViewById(R.id.noTaskFound);
addTaskBtn = view.findViewById(R.id.fab);
addTaskBtn.setOnClickListener(v -> {
Intent intent = new Intent(getContext(), AddTaskActivity.class);
startActivity(intent);
});
calendarTasksAdapter = new CalendarTasksAdapter(getActivity());
recyclerView.setAdapter(calendarTasksAdapter);
fetchTodayPendingTasks();
}
public void fetchTodayPendingTasks() {
JSONObject todayTasksFromDB = Utils.getTodayPendingTasksFromDB();
if (todayTasksFromDB != null) {
noTaskFoundTV.setVisibility(View.GONE);
recyclerView.setVisibility(View.VISIBLE);
try {
JSONArray tasks = todayTasksFromDB.getJSONArray("tasks");
adapter.setData(tasks)
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
#Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
}
#Override
public void onDataUpdate() {
//this toast never triggers/shown when the task is created from the activity
Toast.makeText(getContext(), "Triggered", Toast.LENGTH_SHORT).show();
}
}
and you should define a setData method in your adapter. Do not forget to call notifyDataSetChanged().
public void setData(JSONArray array){
// set to your data list
notifyDataSetChanged();
}

SavedInstance of multiple customViews inside one fragment

I have multiple instances of the same CustomView inside one fragment.
I implemented savedInstance for this CustomView but the problem is since there are multiple instances of this CustomView, savedInstance of the last one, overrides them all.
for example, if there are 3 instances of this CustomView which has a recyclerview inside, If I scroll the last one, it applies to them all. because i'm using key value pairs and the key is the same for all of them. (I can change the key to differ for each one but I think there is a better way)
Here is the code for savedInstance saving and restoring inside my CustomView:
#Nullable
#Override
protected Parcelable onSaveInstanceState() {
Bundle bundle = new Bundle();
bundle.putParcelable(SavedInstanceKey.SUPERSTATE.name(), super.onSaveInstanceState());
bundle.putParcelable(SavedInstanceKey.RECYCLERVIEW.name(), recyclerView.getLayoutManager().onSaveInstanceState()); // ... save stuff
return bundle;
}
#Override
protected void onRestoreInstanceState(Parcelable state) {
if (state instanceof Bundle) // implicit null check
{
Bundle bundle = (Bundle) state;
this.recyclerView.getLayoutManager().onRestoreInstanceState(bundle.getParcelable(SavedInstanceKey.RECYCLERVIEW.name())); // ... load stuff
state = bundle.getParcelable(SavedInstanceKey.SUPERSTATE.name());
}
super.onRestoreInstanceState(state);
}
and here is my fragment's OnCreateView:
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
final View view = inflater.inflate(R.layout.fragment_artist, container, false);
final GridListView gv_new = view.findViewById(R.id.gridlist_new_songs);
final GridListView gv_best = view.findViewById(R.id.gridlist_best);
final GridListView gv_singles = view.findViewById(R.id.gridlist_singles);
final GridListView gv_feats = view.findViewById(R.id.gridlist_feats);
final RecyclerView rc_albums = view.findViewById(R.id.rcview_album);
if(!alreadyInitialized) {
alreadyInitialized = true;
apiService = new ApiService(getContext());
try {
artistID = getArguments().getString(KeyIntent.ARTIST.name());
} catch (Exception e) {
Log.e(TAG, "onCreateView: Artist Fragment doesnt have args.\t", e);
}
apiService.getArtist(artistID, new ApiService.OnArtistReceived() {
#Override
public void onSuccess(Artist artist) {
ArtistFragment.this.artist=artist;
setArtistToViews(artist, view);
}
#Override
public void onFail() {
Toast.makeText(getContext(), "Error on receiving artist.", Toast.LENGTH_LONG).show();
}
});
apiService.getNewSongs(artistID, new ApiService.OnSongsReceived() {
#Override
public void onSuccess(List<Song> songs) {
ArtistFragment.this.newSongs=songs;
List<GridListable> gridListables = new ArrayList<>();
gridListables.addAll(songs);
gv_new.load(gridListables, 1);
}
#Override
public void onFail(ApiService.ApiResponse response) {
Toast.makeText(getContext(), "Error on receiving artist.", Toast.LENGTH_LONG).show();
}
});
apiService.getBestSongs(artistID, new ApiService.OnSongsReceived() {
#Override
public void onSuccess(List<Song> songs) {
ArtistFragment.this.bestSongs=songs;
List<GridListable> gridListables = new ArrayList<>();
gridListables.addAll(songs);
gv_best.load(gridListables, 1);
}
#Override
public void onFail(ApiService.ApiResponse response) {
Toast.makeText(getContext(), "Error on receiving artist.", Toast.LENGTH_LONG).show();
}
});
apiService.getSingleSongs(artistID, new ApiService.OnSongsReceived() {
#Override
public void onSuccess(List<Song> songs) {
ArtistFragment.this.singleSongs=songs;
List<GridListable> gridListables = new ArrayList<>();
gridListables.addAll(songs);
gv_singles.load(gridListables, 1);
}
#Override
public void onFail(ApiService.ApiResponse response) {
}
});
apiService.getFeats(artistID, new ApiService.OnSongsReceived() {
#Override
public void onSuccess(List<Song> songs) {
ArtistFragment.this.feats=songs;
List<GridListable> gridListables = new ArrayList<>();
gridListables.addAll(songs);
gv_feats.load(gridListables, 1);
}
#Override
public void onFail(ApiService.ApiResponse response) {
}
});
apiService.getAlbums(artistID, new ApiService.OnAlbumsReceived() {
#Override
public void onSuccess(List<Album> albums) {
ArtistFragment.this.albums=albums;
List<Projective> projectives = new ArrayList<>();
projectives.addAll(albums);
rc_albums.setAdapter(new AlbumAdapter(getContext(), projectives));
rc_albums.setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, true));
}
#Override
public void onFail(ApiService.ApiResponse response) {
Toast.makeText(getContext(), "Loading albums failed.", Toast.LENGTH_SHORT).show();
}
});
}else {
Log.i(TAG, "onCreateView: Fragment already initialized, restoring from existing artist");
setArtistToViews(artist,view);
gv_new.load(new ArrayList<>(newSongs),1);
gv_best.load(new ArrayList<>(bestSongs),1);
gv_singles.load(new ArrayList<>(singleSongs),1);
gv_feats.load(new ArrayList<>(feats),1);
rc_albums.setAdapter(new AlbumAdapter(getContext(), new ArrayList<>(albums)));
rc_albums.setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, true));
}
return view;
}
I think the problem is the Keys that you use for your Bundles. All your instances of the custom view use the same SavedInstanceKey.SUPERSTATE.name().
You could try to have the Fragment pass a different key to each of the custom views (BEST, NEW...). This way, each of your GridView has its own unique key to use in the saveInstanceState and restoreInstanceState methods.

how to recall android fragment without run again JSON?

I want if fragemnt is recall, the fragment show view it first call not run json again. my code after i open fragment again the fragment is request json again. I am a beginner in java/android programming but I was trying to add different tutorials to create a customized application that does what I want, this could be something easy to most of the people here but am stuck
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
mParam1 = getArguments().getString(ARG_PARAM1);
mParam2 = getArguments().getString(ARG_PARAM2);
}
// Creating volley request obj
JsonArrayRequest billionaireReq = new JsonArrayRequest(getURL,
new Response.Listener<JSONArray>() {
#Override
public void onResponse(JSONArray response) {
url_maps = new HashMap<String, String>();
// Parsing json
for (int i = 0; i < response.length(); i++) {
try {
JSONObject obj = response.getJSONObject(i);
url_maps.put(obj.getString("title") + " - " + obj.getString("releaseYear"), obj.getString("image"));
} catch (JSONException e) {
e.printStackTrace();
}
}
for (String name : url_maps.keySet()) {
TextSliderView textSliderView = new TextSliderView(getActivity());
// initialize a SliderLayout
textSliderView
.description(name)
.image(url_maps.get(name))
.setScaleType(BaseSliderView.ScaleType.CenterCrop)
.setOnSliderClickListener(HomeFragment.this);
//add your extra information
textSliderView.bundle(new Bundle());
textSliderView.getBundle().putString("extra", name);
mSlider.addSlider(textSliderView);
}
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
Toast.makeText(getActivity(), "network issue: please enable wifi/mobile data", Toast.LENGTH_SHORT).show();
}
});
// Adding request to request queue
AppController.getInstance().addToRequestQueue(billionaireReq);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_home, container, false);
mSlider = (SliderLayout) view.findViewById(R.id.slider_home);
gridView = (GridView) view.findViewById(R.id.grid);
gridView.setAdapter(new CustomAndroidGridViewAdapter(getActivity(), gridViewStrings, gridViewImages));
mSlider.setPresetTransformer(SliderLayout.Transformer.Stack);
mSlider.setPresetIndicator(SliderLayout.PresetIndicators.Center_Top);
mSlider.setCustomAnimation(new DescriptionAnimation());
mSlider.setDuration(4000);
return view;
}
"my code after i open fragment again the fragment is request json again"
What i got from your question is, you don't want to request json again if you already visited same fragment in the past. you can do do one thing, When you visit fragment first time save to json object into the Bundle in the saveStateInstance(Bundle bundle) override method and when you revisit fragment check and extract json from paramer bundle of onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) method. In this way you can maintain state of the fragment.
Override onSaveInstanceState method to save instance of your json. In your case, uou have store it in String variable or in object that implements Parcelable interface. List of valid data structures to save is here:
https://developer.android.com/reference/android/os/Bundle.html
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString("yourString", yourJsonInstance);
outState.putString("yourParcelableObj", yourParcelableObj);
//some other variables to save state
}
You have to ways to restore your data, you can do it by override onRestoreInstanceState method. Or you can do it in onCreate method by checking if savedInstanceState store your data:
this runs after onCreate:
#Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
// Restore data here
yourJsonString = savedInstanceState.getString("yourString");
}
or this you can put in your onCreate method:
if (savedInstanceState != null) {
// Restore data here
yourJsonString = savedInstanceState.getString("yourString");
} else{
//else get your json normally
}
If you are using Viewpager then just add
pager.setOffscreenPageLimit(limit)
Refer this post

Make volley http request during app installation

I want to make a volley http request only once and it should be during the time the app is installed.
I achieved this by making the http request in onCreate() method of SQLiteOpenHelper class which fetch data from remote MySQL ready for use. The problem I however runs into is that, after the app installation finishes, the app is presented with blank screen(fragment hosted on the main Activity). But when I close the app and opens for the second time, it is able to fetch data from the SQLite onto the screen.
Is there something special I have to do in the onCreate() method to ensure that the app runs only after the volley request finishes?
Here is my code.
SQLiteOpenHelper onCreate()
#Override
public void onCreate(final SQLiteDatabase db) {
db.execSQL(CREATE_NOTICE_TABLE);
db.execSQL(CREATE_ROSTER_TABLE);
/*Perform One time sync operations from remote MySQL*/
requestQueue = Volley.newRequestQueue(ContextGetter.getAppContext());
JsonObjectRequest request = new JsonObjectRequest(Request.Method.GET, URL, null, new Response.Listener<JSONObject>() {
#Override
public void onResponse(JSONObject response) {
if(response == null || response.length() == 0){
return;
}
if(response.has("notices")){
//Save to notices table
try {
JSONArray notices = response.getJSONArray("notices");
for (int i = 0; i < notices.length(); i++) {
JSONObject noticeObject = notices.getJSONObject(i);
String noticeID = noticeObject.getString(NOTICE_ID_KEY);
String noticeTitle = noticeObject.getString(NOTICE_TITLE_KEY);
String noticeBody = noticeObject.getString(NOTICE_BODY_KEY);
String dateCreated = noticeObject.getString(NOTICE_DATE_KEY);
NoticeItem noticeItem = new NoticeItem();
noticeItem.setId(Integer.parseInt(noticeID));
noticeItem.setTitle(noticeTitle);
noticeItem.setBody(noticeBody);
try {
noticeItem.setDate(formatDate(dateCreated));
} catch (ParseException e) {
e.printStackTrace();
}
//Save to SQLite
createNoticeBoard(noticeItem, db);
}
} catch (JSONException e) {
Log.d(TAG, "JSONException: " + e.getMessage());
}
}
//If roster available
if(response.has("rosters")){
//Save to roster table
try {
JSONArray rosters = response.getJSONArray("rosters");
for (int i = 0; i <rosters.length() ; i++) {
JSONObject rosterObject = rosters.getJSONObject(i);
String rosterID = rosterObject.getString(ROSTER_ID_KEY);
String rosterOwner = rosterObject.getString(ROSTER_OWNER_KEY);
String rosterDate = rosterObject.getString(ROSTER_DATE_KEY);
String rosterShift = rosterObject.getString(ROSTER_SHIFT_KEY);
//Check to verify that the user actually owns that roster later by using shared preference
RosterItem rosterItem = new RosterItem();
rosterItem.setSyncNumber(Integer.parseInt(rosterID));
rosterItem.setStaffNumber(rosterOwner);
rosterItem.setShift(rosterShift);
try {
rosterItem.setDate(formatDate(rosterDate));
} catch (ParseException e) {
e.printStackTrace();
}
createRoster(rosterItem, db);
}
}catch(JSONException e){
Log.d(TAG, "JSONException: "+ e.getMessage());
}
}
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
Log.d(TAG, "VolleyError "+error.getMessage());
}
});
//Add to requestQueue
requestQueue.add(request);
}
Fragment class
public class NoticeListFragment extends Fragment{
private static final String TAG = "NoticeListFragment";
private RecyclerView recyclerView;
private NoticeListAdapter mNoticeListAdapter;
public NoticeListFragment() {
//Requires empty public constructor
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "Notices onCreate() called");
}
#Override
public void onResume() {
super.onResume();
updateUI(); //In case data changes
Log.d(TAG, "onResume() called");
}
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
//Inflate layout for this fragment
View rootView = inflater.inflate(R.layout.fragment_notice_list, container, false);
recyclerView = (RecyclerView) rootView.findViewById(R.id.rv_recycler_view);
recyclerView.setHasFixedSize(true);
LinearLayoutManager linearManager = new LinearLayoutManager(getActivity());
recyclerView.setLayoutManager(linearManager);
updateUI();
return rootView;
}
/*View Holder*/
private class NoticeViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener{
private NoticeItem mNoticeItem;
public CardView mCardView;
public TextView mTextViewTitle;
public TextView mTextViewDate;
public TextView mTextViewBody;
public NoticeViewHolder(View itemView) {
super(itemView);
mCardView = (CardView) itemView.findViewById(R.id.card_view);
mTextViewBody = (TextView) itemView.findViewById(R.id.tv_notice_summary);
mTextViewTitle = (TextView) itemView.findViewById(R.id.tv_notice_title);
mTextViewDate = (TextView) itemView.findViewById(R.id.tv_notice_date);
itemView.setOnClickListener(this);
}
//Bind properties to views
private void bindNotice(NoticeItem noticeItem){
mNoticeItem = noticeItem;
mTextViewTitle.setText(noticeItem.getTitle());
mTextViewDate.setText(noticeItem.getDate());
mTextViewBody.setText(noticeItem.getSummary());
}
#Override
public void onClick(View view) {
Intent intent = NoticePagerActivity.newIntent(getActivity(), mNoticeItem.getId());
startActivity(intent);
}
}
/*Adapter*/
private class NoticeListAdapter extends RecyclerView.Adapter<NoticeViewHolder>{
//private Context mContext;
private List<NoticeItem> listItems;
//Provide a suitable constructor (depends on the kind of dataset you have)
public NoticeListAdapter(List<NoticeItem> data) {
//this.mContext = context;
this.listItems = data;
}
#Override
public NoticeViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
//Create a new view
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.notice_lists_card, parent, false);
//Set the view size, margin, padding and layout parameters
NoticeViewHolder vh = new NoticeViewHolder(view);
return vh;
}
#Override
public void onBindViewHolder(NoticeViewHolder holder, int position){
final NoticeItem noticeItem = listItems.get(position);
//Bind data properties to views here...
holder.bindNotice(noticeItem);
}
#Override
public int getItemCount() {
return listItems.size();
}
public void setNotices(List<NoticeItem> notices){
listItems = notices;
}
}
//Bind adapter to recycler view
private void updateUI(){
NoticeLab noticeLab = NoticeLab.get(getActivity());
List<NoticeItem> notices = noticeLab.getNotices();
if(mNoticeListAdapter == null){
mNoticeListAdapter = new NoticeListAdapter(notices);
recyclerView.setAdapter(mNoticeListAdapter);
}else{
mNoticeListAdapter.setNotices(notices);
mNoticeListAdapter.notifyDataSetChanged();
}
}
}
I want to make a volley http request only once and it should be during the time the app is installed.
You do not get control when your app is installed.
Is there something special I have to do in the onCreate() method to ensure that the app runs only after the volley request finishes?
Volley is asynchronous. That is the complete and entire point behind using Volley. Immediately after you call requestQueue.add(request);, your onCreate() method continues executing, while Volley performs the network I/O on a background thread.
Some options are:
Get rid of all the Volley code, by packaging your starter data in the APK as an asset and using SQLiteAssetHelper to deploy the packaged database on first run of your app.
Do not use Volley. Instead, use something with a synchronous network I/O option (HttpURLConnection, OkHttp, etc.), and perform synchronous network I/O here. You should always be using your SQLiteOpenHelper subclass on a background thread, in case the database needs to be created or updated. So your onCreate() method of your SQLiteOpenHelper should always be called on a background thread, and you would not need yet another background thread for the network I/O. Then, you can be sure that by the time onCreate() ends that your starter data is there... except if you do not have Internet connectivity, or your server is down, etc.
Move all your initialization logic to something else, such as an IntentService. Have it create the database (using the IntentService's own background thread) and have it do the network I/O (again, using a synchronous API, since IntentService has its own background thread). Only start your UI once the IntentService is done with its work. You are in better position here to deal with connectivity errors via some sort of retry policy, while presenting some temporary UI to the user while that work is going on (e.g., ProgressBar).

Drawer + Tab + ViewPager and each fragment load data from server

Some menu in the drawer will open Tab + ViewPager content. Each page (fragment) is list that its data is requested from server.
Every time I click that menu, I want the content will show the tab immediately even the data are still requested instead empty screen. I try to add progress bar in the TabFragment so the content will show a loading when preparing the ViewPager and the pagers' data. But, the content still show an empty screen without loading indicator. I found the problem is because the method to request data from server is called from each pager.
Should I move the method for requesting data to TabFragment?
My TabFragment class looks like:
public void onViewCreated(View view, #Nullable Bundle savedInstanceState) {
.....
content = view.findViewById(R.id.content);
content.setVisibility(View.GONE);
tabLayout = (TabLayout) view.findViewById(R.id.tabs);
viewPager = (ViewPager) view.findViewById(R.id.pager);
progressBar = (ProgressBar) view.findViewById(R.id.progressBar1);
farmerViewPagerAdapter = new FarmerViewPagerAdapter(getChildFragmentManager(), titles);
viewPager.setOffscreenPageLimit(2);
viewPager.setAdapter(farmerViewPagerAdapter);
tabLayout.post(()->{
tabLayout.setupWithViewPager(viewPager);
for (int i = 0; i < titles.length; ++i){
tabLayout.getTabAt(i).setIcon(icons[i]);
}
});
}
And here is fragment for each page (each page requests different data):
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
this.view = view;
emptyView = view.findViewById(R.id.emptyView);
emptyText = (TextView) view.findViewById(R.id.emptyTextView);
recyclerView = (RecyclerView) view.findViewById(R.id.list);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
recyclerView.setItemAnimator(new DefaultItemAnimator());
adapter = new FarmerAdapter(data, getContext());
recyclerView.setAdapter(adapter);
recyclerView.addItemDecoration(
new HorizontalDividerItemDecoration.Builder(getContext())
.showLastDivider()
.marginResId(R.dimen.divider_margin_left, R.dimen.divider_margin_right)
.build());
swipeRefreshLayout = (SwipeRefreshLayout) view.findViewById(R.id.swipeRefreshLayout);
swipeRefreshLayout.setOnRefreshListener(() -> {
if (!Util.isNetworkAvailable(getContext())) {
if (swipeRefreshLayout.isRefreshing()) swipeRefreshLayout.setRefreshing(false);
} else {
currentPage = 1;
loadData(); //method to request data from server
}
}
);
if (user != null) {
getDataFromLocal();
addToAdapter();
loadData();
}
}
(Ed)loadData method :
Observable<Response<List<Data>>> dataApi = request.getServerData(currentPage,
NUMBER_DATA_PER_PAGE,
token);
dataApi.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(responseData -> {
if (swipeRefreshLayout.isRefreshing()) swipeRefreshLayout.setRefreshing(false);
if (responseData.isSuccessful() && responseData.code() == 200) {
currentPage++;
adapter.add(responseData.body());
if (adapter.getItemCount() < 1) {
emptyText.setText("Empty");
emptyView.setVisibility(View.VISIBLE);
}
} else {
try {
JSONObject json = new JSONObject(responseFarmer.errorBody().string());
Toast.makeText(getContext(), json.getString("message"), Toast.LENGTH_LONG).show();
} catch (Exception e) {
e.printStackTrace();
}
}
}, error -> {
if (swipeRefreshLayout != null && swipeRefreshLayout.isRefreshing())
swipeRefreshLayout.setRefreshing(false);
if (error != null && error.getLocalizedMessage() != null)
Toast.makeText(getContext(), error.getLocalizedMessage(), Toast.LENGTH_LONG).show();
});
This method is called from pager fragment.
Your AsyncTask has an empty doInBackground() body. That essentially makes it synchronous. Say you have this AsyncTask:
private class SetAdapterTask extends AsyncTask<Void,Void,Void> {
protected Void doInBackground(Void... params) {
return null;
}
#Override
protected void onPostExecute(Void result) {
doPostExecute();
}
#Override
protected void onPreExecute() {
doPreExecute();
}
}
and you call this in your code like this:
public void onViewCreated(View view, #Nullable Bundle savedInstanceState) {
.....
new SetAdapterTask().execute();
}
but since your AsyncTask doesn't do anything in background, the postExecute fires off right after the preExecute, making the whole thing equivalent to this:
public void onViewCreated(View view, #Nullable Bundle savedInstanceState) {
.....
doPreExecute();
doPostExecute();
}
In other words, you make your ProgressBar visible in preExecute and immediatelly after that you make it disappear in postExecute.
The right way to approach this would be moving the ProgressBar visibility settings to the AsyncTask you use to load your data, which is located somewhere in the loadData() I presume. As for the data loading itself, it's hard to say what is wrong without seeing the actual methods which load the data.

Categories

Resources