I have a listview inside a fragment showing weather data(image,temperature and weather description). I fetch the data from here
When I rotate the emulator though I get a crash.:(. Here is my code. I use the setRetainedInstance but with out any result. Somehow I need to save and restore the asynchronous data I get,but I don't know how.
public class ForecastFragment extends Fragment{
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+" 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);
requestData("http://api.openweathermap.org/data/2.5/forecast/daily?lat=50.09&lon=14.42&cnt=9&&units=metric&mode=json");
}
//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 {
String 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);
}
}
}
Here is my logcat.
08-01 19:45:59.857 21260-21260/com.theotziomakas.weatherapp E/AndroidRuntime﹕ FATAL EXCEPTION: main
java.lang.NullPointerException
at android.widget.ArrayAdapter.init(ArrayAdapter.java:310)
at android.widget.ArrayAdapter.<init>(ArrayAdapter.java:153)
at com.theotziomakas.weatherapp.Fragments.WeatherAdapter.<init>(WeatherAdapter.java:34)
at com.theotziomakas.weatherapp.Fragments.ForecastFragment$MyTask.onPostExecute(ForecastFragment.java:144)
at com.theotziomakas.weatherapp.Fragments.ForecastFragment$MyTask.onPostExecute(ForecastFragment.java:98)
at android.os.AsyncTask.finish(AsyncTask.java:631)
at android.os.AsyncTask.access$600(AsyncTask.java:177)
at android.os.AsyncTask$InternalHandler.handleMessage(AsyncTask.java:644)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:4867)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1007)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:774)
at dalvik.system.NativeStart.main(Native Method)
You need to implement onSaveInstanceState to save the data, then retrieve it in onCreateView
#Override
public void onSaveInstanceState(Bundle savedInstanceState) {
savedInstanceState.putString("YourVariableName", yourVariable);
super.onSaveInstanceState(savedInstanceState);
}
Then in onCreateView
if (savedInstanceState != null) {
yourVariable = savedInstanceState.getString("YourVariableName");
}
Check out the documentation for more info
Related
I have a ListFragment which fetches data from the net using a Loader. I use a new instance of this ListFragment in every page of my ViewPager. It works perfectly, but when I use TabLayout or moves pages quickly, the Fragment keeps loading and does not display the data in the ListView.
When I checked using log messages, I found that the ListFragment skips some lines of code in the onLoadFinished() method. It does not make the ProgressBar invisible. It does add items to Adapter, but it is not being displayed in the ListView. This problem also happens in the first page of the ViewPager.
Is there any specific rule to be followed when using ListFragments in a ViewPager?
Here is the ListFragment class. If you look at the onLoadFinished() method, you can see the lines causing problem:
public class ListViewFragment extends ListFragment
implements LoaderManager.LoaderCallbacks<List<GameNews>> {
public static ListViewFragment newInstance(String url) {
Log.d("ListViewFragment", "newInstance created");
ListViewFragment f = new ListViewFragment();
// Supply url input as an argument.
Bundle args = new Bundle();
args.putString("url", url);
f.setArguments(args);
return f;
}
List<GameNews> TotalNews;
ListView gameListView;
LinearLayout emptyView;
Button retryButton;
ListAdapter adapter ;
private View progressBar;
final private int game_loader = 0;
ArrayList<String> urls = new ArrayList<>();
String mUrlString;
int index;
//LIFE CYCLE OF FRAGMENT
//------------------------------------------------------------------
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mUrlString = getArguments().getString("url");
urls.add(mUrlString);
TotalNews = new ArrayList<GameNews>();
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_list_view, container, false);
ArrayList<GameNews> gameList = new ArrayList<>();
adapter = new ListAdapter(getActivity(), gameList);
return rootView;
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
emptyView = (LinearLayout)
view.findViewById(R.id.no_internet_view);
progressBar = view.findViewById(R.id.progress_bar);
retryButton = (Button) view.findViewById(R.id.retry_button);
gameListView = getListView();
emptyView.setVisibility(View.INVISIBLE);
}
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setListAdapter(adapter);
//If connected to net start the loader
if (isConnected()) {
getActivity().getSupportLoaderManager().restartLoader(game_loader,
null,
ListViewFragment.this);
}
}
//OVERRIDED METHODS OF LOADERMANAGER
//---------------------------------------------------------------------
#Override
public android.support.v4.content.Loader<List<GameNews>> onCreateLoader(int i, Bundle bundle) {
AdManager manager = new AdManager(getActivity());
return new FragmentLoader(getActivity(), urls, 1000);
}
//HERE IS THE PROBLEM PLEASE FOCUS INSIDE THIS METHOD
//-------------------------------------------------------
#Override
public void onLoadFinished(Loader<List<GameNews>> loader, List<GameNews> games) {
progressBar.setVisibility(View.INVISIBLE); //This line of code is not executed
adapter.clear();
TotalNews.addAll(games);
adapter.addAll(games);//And the listView is not populated
}
//-------------------------------------------------------
#Override
public void onLoaderReset(Loader<List<GameNews>> loader) {
adapter.clear();
}
//REUSABLE METHODS
//------------------------------------------------------------------
//Method checks if there is internet
public boolean isConnected() {
ConnectivityManager manager = (ConnectivityManager)
getActivity().getSystemService(CONNECTIVITY_SERVICE);
NetworkInfo info = manager.getActiveNetworkInfo();
if (info != null && info.isConnected()) {
return true;
}
else {
return false;
}
}
}
Your Fragment class is using the Activity's LoaderManager:
getActivity().getSupportLoaderManager().restartLoader(...);
And each instance is using the same ID in its restartLoader() call:
final private int game_loader = 0;
This means that each Fragment instance was using and restarting the same Loader over and over again, leading to the weird behavior you observed.
The solution is quite simple: use Fragment's local LoaderManager, instead of the Activity's.
getLoaderManager().restartLoader(...);
With this, you don't need to worry about changing the ID in each instance, since Loaders are unique to their Fragment, and the Loader will be properly handled over the Fragment's lifetime, which would likely not have been the case when using the Activity's LoaderManager.
I'm trying to parse a RSS feed and display it to the user via a Listview which is in a Fragment. The problem is that nothing shows up on the list and I get a few errors:
V/Error Parsing Data: java.io.IOException: Couldn't open http://feeds.bbci.co.uk/news/england/rss.xml
W/FragmentManager: moveToState: Fragment state for Thingy{3633e4e #1 id=0x7f0c0069} not updated inline; expected state 3 found 2
My Fragments class:
public class Thingy extends Fragment {
ListView mList;
ArrayAdapter<String> adapter;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_main, container, false);
adapter = new ArrayAdapter<>(getActivity(), R.layout.basic_list_item);
mList = ((ListView) v.findViewById(R.id.listView));
new GetRssFeed().execute("http://feeds.bbci.co.uk/news/england/rss.xml");
return v;
}
private class GetRssFeed extends AsyncTask<String, Void, Void> {
#Override
protected Void doInBackground(String... params) {
adapter.add("Pizza Steak");
try {
RssReader rssReader = new RssReader(params[0]);
for (RssItem item : rssReader.getItems()) {
adapter.add(item.getTitle());
}
} catch (Exception e) {
Log.v("Error Parsing Data", e + "");
}
return null;
}
#Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
adapter.notifyDataSetChanged();
mList.setAdapter(adapter);
}
}
}
Please add the permission on the manifest
<manifest xlmns:android...>
...
<uses-permission android:name="android.permission.INTERNET" />
<application ...
</manifest>
The problem may be in the URL that you are using where it isn't a valid RSS feed url,
so that, try to run your code by the this url: http://www.aljazeera.com/xml/rss/all.xml (for testing only).
I'm having problems to implement an onclick to the items I have in my list. the error is below:
06-14 16:15:43.861 11651-11651/wk.gon250.dublinbike E/AndroidRuntime﹕ FATAL EXCEPTION: main
Process: wk.gon250.dublinbike, PID: 11651
java.lang.RuntimeException: Don't call setOnClickListener for an AdapterView. You probably want setOnItemClickListener instead
at android.widget.AdapterView.setOnClickListener(AdapterView.java:778)
at tab.Tab2.onCreateView(Tab2.java:67)
The code of my class is:
public class Tab2 extends Fragment
{
static ListView listView;
static CustomAdapter adapter;
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.tab_2, container, false);
String Url = "http://mypath";
RequestQueue queue = Volley.newRequestQueue(getActivity());
final ProgressDialog progressDialog = ProgressDialog.show(getActivity(), "Please, Wait", "Loading..");
listView = (ListView) v.findViewById(R.id.listViewT_stations);
JsonObjectRequest req = new JsonObjectRequest(Url, new Response.Listener<JSONObject>() {
#Override
public void onResponse(JSONObject response) {
try {
Gson gson = new Gson();
JSONObject jsonNetwork = new JSONObject(response.getString("network"));
JSONArray stationsArray = jsonNetwork.getJSONArray("stations");
StationJson[] stations = gson.fromJson(stationsArray.toString(), StationJson[].class);
adapter = new CustomAdapter(getActivity(), stations, R.layout.station_item);
listView.setAdapter(adapter);
} catch (JSONException e) {
e.printStackTrace();
}
progressDialog.cancel();
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
//Manage Error
progressDialog.cancel();
}
});
queue.add(req);
listView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
}
});
return v;
} }
I can't no use a listFragment, so any sugestion? How can I implement a onclick to the items I have to open a new view? and what is the best practice?
Thanks!!
The error message says:
Don't call setOnClickListener for an AdapterView. You probably want setOnItemClickListener instead
The OnClickListener is meant for click events on whole views. In an AdapterView, you usually want to register click events for each item separately. For this you need to use OnItemClickListener.
Basically the flow is like the following: when a fragment is created (or when the user swipes to refresh the layout), an AsyncTask is executed. The ASyncTask retrieves the info from a URL and setup an adapter which is then assigned to a ListView.
This is the code of the Fragment:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.tasks_page, container, false);
mSwipeRefreshLayout = (SwipeRefreshLayout)
rootView.findViewById(R.id.list_container);
mSwipeRefreshLayout.setOnRefreshListener(this);
mSwipeRefreshLayout.setColorSchemeResources(android.R.color.holo_blue_bright,
android.R.color.holo_green_light, android.R.color.holo_red_light);
lv = (ListView) rootView.findViewById(android.R.id.list);
getTasks();
return rootView;
}
private void getTasks() {
new TasksRetriever(mSwipeRefreshLayout,tasksAdapter,lv).execute();
}
#Override
public void onRefresh() {
mSwipeRefreshLayout.setRefreshing(true);
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
getTasks();
}
}, 1000);
}
The TaskRetriever constructor is:
public TasksRetriever(SwipeRefreshLayout srl, TasksAdapter ta, ListView lv) {
adpt = ta;
this.srl = srl;
this.lv = lv;
}
and postExecute is:
#Override
protected void onPostExecute(List<TasksItem> result) {
super.onPostExecute(result);
dialog.dismiss();
if (adpt == null) adpt = new TasksAdapter(result);
else adpt.setItemList(result);
lv.setAdapter(adpt);
adpt.notifyDataSetChanged();
srl.setRefreshing(false);
}
Not sure yet if it works but I was wondering if I'm on the right track because it doesn't look clean to me. On the other hand, I can't create the adapter and assign it to the ListView until I actually have data for it... Is this considering OK or am I doing it wrong?
I would set the list adapter up in the onCreate() method under your list view and make this a instance variable (not a local one) so that the Async task can access it as follows i.e.
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
lv = (ListView) rootView.findViewById(android.R.id.list);
mAdpt = new TasksAdapter(null);
lv.setAdapter(mAdpt);
}
And then in your onPostExecute()
#Override
protected void onPostExecute(List<TasksItem> result) {
dialog.dismiss();
mAdpt.clear();
for( TaskItem ti : result )
{
mAdpt.add( ti );
}
mAdpt.notifyDataSetChanged();
srl.setRefreshing(false);
}
Also make sure you account for the null value being passed into your adapter in the getView() method.
Hope this helps.
you CAN create an empty adapter and fill it later - in AsyncTask
your onPostExecute() should look like:
#Override
protected void onPostExecute(List<TasksItem> result) {
dialog.dismiss();
if (adpt == null){
adpt = new TasksAdapter(result);
lv.setAdapter(adpt);
}else{
adpt.clear();
}
for( TaskItem ti : result ) adpt.add( ti );
adpt.notifyDataSetChanged();
srl.setRefreshing(false);
}
I got 3 swipeable empty Views. The 3rd View is the "ViewAll". The first View works fine but when I swipe to the second it shows this error.
I've tried to display data that I loaded with JSON in a AsyncTask, but there is this NullPointerException error
and I don't know how to solve it...
11-12 10:00:23.046: E/InputEventReceiver(12007): Exception dispatching input event.
11-12 10:00:23.046: E/MessageQueue-JNI(12007): Exception in MessageQueue callback: handleReceiveCallback
11-12 10:00:23.056: E/MessageQueue-JNI(12007): java.lang.NullPointerException
11-12 10:00:23.056: E/MessageQueue-JNI(12007): at com.example.rumorz.ViewAll$PostTask.onPreExecute(ViewAll.java:52)
11-12 10:00:23.056: E/MessageQueue-JNI(12007): at android.os.AsyncTask.executeOnExecutor(AsyncTask.java:586)
Here is my code:
public class ViewAll extends Fragment {
//URL to get JSON Array
private static String url = ""; //URL to my site with JSON-code
//JSON Node Names
private static final String TAG_USER = "user";
private static final String TAG_message= "message";
TextView tvuser ;
TextView tvmessage ;
JSONArray user = null;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
new PostTask().execute();
View rootView = inflater.inflate(R.layout.viewall, container, false);
return rootView;
}
private class PostTask extends AsyncTask<String, String, JSONObject> {
#Override
protected void onPreExecute() {
super.onPreExecute();
tvuser = (TextView)getView().findViewById(R.id.tvUser);
tvmessage = (TextView)getView().findViewById(R.id.tvmessage);
}
#Override
protected JSONObject doInBackground(String... args) {
JSONParser jParser = new JSONParser();
// Getting JSON from URL
JSONObject json = jParser.getJSONFromUrl(url);
return json;
}
#Override
protected void onPostExecute(JSONObject json) {
try {
user = json.getJSONArray(TAG_USER);
JSONObject c = user.getJSONObject(0);
// Storing JSON item in a Variable
String users = c.getString(TAG_USER);
String message = c.getString(TAG_message);
tvuser.setText(users);
tvmessage.setText(message);
} catch (JSONException e) {
e.printStackTrace();
}
}
}
onPreExecute(), invoked on the UI thread immediately after the task is executed.
intialize
tvuser = (TextView)rootView.findViewById(R.id.tvUser);
tvmessage = (TextView)rootview.findViewById(R.id.tvmessage);
in onCreateView
Initialize it in onCreateView. Use the inflated view object to initialize your textview's. or use getView in onActivityCreated to initialize textview's.
Since the textview is declared as a class member you can use it in onPostEecute to set text to the same.
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.viewall, container, false);
tvuser = (TextView)rootView.findViewById(R.id.tvUser);
tvmessage = (TextView)rootview.findViewById(R.id.tvmessage);
new PostTask().execute();
return rootView;
}