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
Related
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.
I want to know how many list items are used in a RecyclerView so I can iterate over the list items and click on them with Espresso. The problem is I'm using Volley asynchronously to get the JSON data I need for the underlying data set of my adapter. I've used an idling resource counter in my UI test to defer the execution of the test until the counter is zero. However, calling the getItemCount on my adapter still results in zero, despite the fact that my networking operation should resolve before the test executes.
UI test code
#Test
public void foo(){
// Register a idling resource counter for Volley to get JSON data asynchronously
Espresso.registerIdlingResources(mActivityTestRule.getActivity().idlingCounter);
RecyclerView v
= (RecyclerView)
mActivityTestRule.getActivity().findViewById(R.id.rv_recycler_view);
int count = v.getAdapter().getItemCount();
// Prints zero; why?
System.out.println("adapter count = "+String.valueOf(count));
}
Here is my onCreate method. The adapter list is initialized as empty, but is populated in onReponse when Volley finishes networking. However, Espresso doesn't seem to wait for onReponse to be called. Not sure why, as I believe I'm using the idling resource counter correctly.
RecyclerView mRecyclerView;
public Adapter mAdapter;
public CountingIdlingResource idlingCounter = new CountingIdlingResource("DATA_LOADER");
#Override
public void onCreate(Bundle savedInstaceState){
// ... some code
// Init an empty array list
ArrayList<MyObject> list = new ArrayList<>();
// Init the adapter with the list, and set that adapter to the view
mRecyclerAdapter = new Adapter(List, MainActivity.this);
mRecyclerView.setAdapter(mRecyclerAdapter);
mRecyclerView.setLayoutManager(new LinearLayoutManager(MainActivity.this));
// Add JSON data to list; increment idlingCounter resource counter.
// decrement idlingCounter in onResponse
idlingCounter.increment();
fetchJson();
}
Edit
MainActivity class
public class MainActivity extends AppCompatActivity {
// Debugging
private static final String TAG = MainActivity.class.getSimpleName();
// Testing
public CountingIdlingResource idlingCounter = new CountingIdlingResource("DATA_LOADER");
// Networking
private RequestQueue requestQueue;
// Data
private ArrayList<Recipe> mRecipes = new ArrayList<>();
// UI
public Adapter mRecyclerAdapter;
#BindView(R.id.rv_recycler_view) RecyclerView mRecyclerView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
getSupportActionBar().setTitle("Recipes");
// Perform networking if there is no saved instance state
if(null == savedInstanceState){
requestQueue = Volley.newRequestQueue(this);
// TODO calling idlingCounter.increment()
idlingCounter.increment();
fetch(requestQueue); // Initializes the recycler view adapter when done fetching
} else {
Log.v(TAG,"null != savedInstanceState");
mRecipes = savedInstanceState.getParcelableArrayList(Constants.KEY_RECIPES);
mRecyclerAdapter = new Adapter(mRecipes, this);
mRecyclerView.setAdapter(mRecyclerAdapter);
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
}
}
#Override
public void onSaveInstanceState(Bundle outState) {
outState.putParcelableArrayList(Constants.KEY_RECIPES,mRecipes);
super.onSaveInstanceState(outState);
}
// Fetch json and build recipe-object array-list
private void fetch(RequestQueue requestQueue) {
// Define the request
JsonArrayRequest request = new JsonArrayRequest(Constants.JsonURL,
new Response.Listener<JSONArray>() {
// Handles JSON response data
#Override
public void onResponse(JSONArray jsonArray) {
Log.v(TAG,"onResponse");
for (int i = 0; i < jsonArray.length(); i++) {
try {
JSONObject jsonObject = jsonArray.getJSONObject(i);
Recipe recipe = new Recipe(jsonObject);
//recipe.mSteps.remove(0);
mRecipes.add(recipe);
Log.v(TAG,jsonObject.toString());
} catch (JSONException e) {
e.printStackTrace();
}
}
// Initialize adapter when JSON data is ready
// TODO initializing the adapter withe the data set
mRecyclerAdapter = new Adapter(mRecipes, MainActivity.this);
mRecyclerView.setAdapter(mRecyclerAdapter);
mRecyclerView.setLayoutManager(new LinearLayoutManager(MainActivity.this));
// Test if this activity was opened from the widget using an intent
Intent intent = getIntent();
if(intent.getExtras() != null){
// Start TwoPaneActivity passing it the intent extra
int position = intent.getIntExtra(WidgetProvider.INTENT_KEY_RECIPE_IDX,-1);
intent = new Intent(MainActivity.this,TwoPaneActivity.class);
Recipe recipe = mRecipes.get(position);
intent.putExtra(Constants.KEY_SINGLE_RECIPE,recipe);
startActivity(intent);
}
// TODO calling idlingCounter.decrement()
idlingCounter.decrement();
}
},
// Error listener object
new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError volleyError) {
Toast.makeText(MainActivity.this, "Unable to fetch data: "
+ volleyError.getMessage(), Toast.LENGTH_SHORT).show();
}
});
// Queue the request
requestQueue.add(request);
}
public void foo(){
}
}
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.
There is a Fragment in my android project in which it gets data dynamically from another class.I save that data(one string and Int) into bundle and And I want that bundle to be restored when screen rotates. So I have used onSaveInstanceState method.
In this fragment, "respond" method get data(one string and Int) from another class. I can print those strings in Logcat in respond method.
Fragment Code:
public class Images extends android.support.v4.app.Fragment implements Imageinfo {
private RecyclerView IRecyclerView;
private RecyclerView.Adapter IAdapter;
private RecyclerView.LayoutManager ILayoutManager;
private Context ctx;
private Bundle bundle=new Bundle();
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v =inflater.inflate(R.layout.images, container, false);
Log.d("ONCREATE VIEW", "TRUE");
IRecyclerView = (RecyclerView) v.findViewById(R.id.images_tab);
IRecyclerView.setHasFixedSize(true);
ILayoutManager = new LinearLayoutManager(getContext());
IRecyclerView.setLayoutManager(ILayoutManager);
if(savedInstanceState!=null){
Log.d("BUNDLE ADDED NOT NULL", String.valueOf(savedInstanceState.size()));
IAdapter = new ImageAdapter(this.getContext(),((fileselect)getContext()).imageset,bundle);
IRecyclerView.setAdapter(IAdapter);
}
else{
Log.d("BUNDLE ADDED NULL", "TRUE");
IAdapter = new ImageAdapter(this.getContext(),((fileselect)getContext()).imageset,null);
IRecyclerView.setAdapter(IAdapter);
}
return v;
}
#Override
public void onSaveInstanceState(Bundle outState) {
outState=bundle;
Log.d("SAVING TIME", String.valueOf(outState.size()));
super.onSaveInstanceState(outState);
}
#Override
public void respond(String str,int type) {
if(str!=null) {
if (type == 1) {
bundle.putString(str, str);
Log.d("BUNDLE ADDED", bundle.getString(str));
Log.d("BUNDLE ADDED Size",String.valueOf(bundle.size()));
} else {
bundle.remove(str);
Log.d("BUNDLE REMOVED Size", String.valueOf(bundle.size()));
}
}
}
}
PROBLEM:
Although the method respond receiving data(one string and Int) and saving into Bundle, bundle is becoming size zero in onSaveInstanceState when screen rotated. onSaveInstanceState is getting called whenever i rotate screen but the bundle is becoming size zero. As bundle is becoming size zero, I could not restore the two strings.
OK the problem is that, instead of adding content to the outstate variable, you are trying to reference a local variable.
The point is that the instance of the Images class is destroyed on rotation, and so will your bundle variable.
The correct way to achieve what you are trying to do is to add your strings to the outstate variable in the onSaveInstanceState method, and read it from the savedInstanceState in the onCreateView.
Try this out:
#Override
public void onSaveInstanceState(Bundle outState) {
outState.putString("YOUR_STRING_NAME", bundle.getString("YOUR_STRING_NAME"));
Log.d("SAVING TIME", String.valueOf(outState.size()));
super.onSaveInstanceState(outState);
}
I have the following piece of code which retrieve some weather data from the openweathermap api. The AsyncTask class is used for that purpose.
public class ForecastFragment extends Fragment {
String imageUrl;
ListView listView;
List<WeatherForecastData> WeatherForecastDataList;
String IMG_URL = "http://api.openweathermap.org/img/w/";
Fragment fragment;
public ForecastFragment() {
// Required empty public constructor
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
//Inflate xml view and convert it to a View object
View rootView = inflater.inflate(R.layout.fragment_forecast, container, false);
//Initialise ListView.
listView = (ListView) rootView.findViewById(R.id.listView);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
String temp = WeatherForecastDataList.get(position).getWeatherTemperature();
Toast.makeText(getActivity(), temp + "° C"+" Have a nice day", Toast.LENGTH_SHORT).show();
}
});
return rootView;
}
//Now we are ready for further processing
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setRetainInstance(true);
if (savedInstanceState == null) {
if(isOnline()) {
requestData("http://api.openweathermap.org/data/2.5/forecast/daily?lat=50.09&lon=14.42&cnt=9&&units=metric&mode=json");
}else{
Toast.makeText(getActivity(),"There is no internet connection",Toast.LENGTH_SHORT).show();
}
}
}
#Override
public void onSaveInstanceState(Bundle savedInstanceState) {
savedInstanceState.putString("ImageURL", imageUrl);
super.onSaveInstanceState(savedInstanceState);
}
//We create a MyTask object,and execute the async. thread with the specified url which is shown just above.
private void requestData(String uri) {
MyTask task = new MyTask();
task.execute(uri);
}
//AsyncTask that will do the asynchronous threading. It displays the weather's icon,description
//and temperature in the main thread via the OnPostExecute(...) method.
private class MyTask extends AsyncTask<String, String, List<WeatherForecastData>> {
#Override
protected void onPreExecute() {
//Used to initialise Views such as Progress Bars which are not needed for this
//project.
}
#Override
protected List<WeatherForecastData> doInBackground(String... params) {
//Read the url,specify the METHOD GET, and store it in content.
String content = HttpManager.getData(params[0]);
//JSON parsing of the openweather api's response. It is not hard,but I had to use the
//debugger quite a lot to make sure that I deserialise the correct JSON values into Strings.
WeatherForecastDataList = WeatherJSONParser.parseFeed(content);
//Fetching the url image
for (WeatherForecastData d : WeatherForecastDataList) {
try {
imageUrl = IMG_URL + d.getPhoto();
InputStream in = (InputStream) new URL(imageUrl).getContent();
Bitmap bitmap = BitmapFactory.decodeStream(in);
//Is it deprecated?
d.setBitmap(bitmap);
in.close();
} catch (Exception e) {
e.printStackTrace();
}
}
return WeatherForecastDataList;
}
//WeatherForecastData is the Object that contains all that instances we want to display.
#Override
protected void onPostExecute(List<WeatherForecastData> result) {
if (result == null) {
Toast.makeText(getActivity(), "There is some wrong,and data can not be displayed", Toast.LENGTH_LONG).show();
return;
}
WeatherForecastDataList = result;
//Display the ListView.
WeatherAdapter adapter = new WeatherAdapter(getActivity(), R.layout.weather_row, WeatherForecastDataList);
listView.setAdapter(adapter);
}
}
protected boolean isOnline() {
ConnectivityManager cm = (ConnectivityManager) getActivity().getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo netInfo = cm.getActiveNetworkInfo();
if (netInfo != null && netInfo.isConnectedOrConnecting()) {
return true;
} else {
return false;
}
}
}
My question is how to make my async task class to work when phone rotates.In other words,I don't want my Fragment to be killed,but storing the weather get I get. I saw other questions here too,but I am confused in this part. Thank you.
Making config changes in the manifest is not the recommended way to save the instance of the fragment.
Instead, you should save the instance of the fragment in container activity's onSaveInstanceState() overriden method.
Below is a small snippet that will help you:
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
getSupportFragmentManager().putFragment(outState,"fragmentInstanceSaved",getSupportFragmentManager().findFragmentById(R.id.fragment_container));
}
Now, in your container activity's onCreate method check if bundle is null or not:
if(savedInstanceState!=null){
Fragment fragment = getSupportFragmentManager().getFragment(savedInstanceState,"fragmentInstanceSaved");
//recreate your preserved fragment here
}else{
//goto ur default activity or fragment....
}