Super hard one to explain.
This is the error I get in my reporting:
Attempt to invoke virtual method 'java.lang.Object android.content.Context.getSystemService(java.lang.String)' on a null object reference
This seems to happen intermittently when you go into and out of the fragment. The error seems to happen in the adaptor.
This is where it is called:
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, Bundle savedInstanceState) {
getActivity().setTitle("Shipments");
myView = inflater.inflate(R.layout.shipments_out_layout, container, false);
listView = myView.findViewById(R.id.listView);
fetchShipments();
return myView;
}
/**
* Fetch shipments
*/
public void fetchShipments()
{
shipmentsService.fetchFromServer(getActivity());
}
/**
* Show shipments
*/
public void showShipments(){
RealmResults<Shipment> savedShipments = shipmentsService.all();
ShipmentsAdaptor adaptor = new ShipmentsAdaptor(savedShipments, this.getContext());
listView.setAdapter(adaptor);
}
And this is where the error is in the adaptor:
public class ShipmentsAdaptor extends ArrayAdapter<Shipment> {
private RealmResults<Shipment> dataSet;
Context mContext;
// View lookup cache
private static class ViewHolder {
TextView stockItemId;
TextView technicianName;
TextView shipmentDate;
}
public ShipmentsAdaptor(RealmResults<Shipment> data, Context context){
super(context, R.layout.shipments_out_row_item, data);
this.dataSet = data;
this.mContext = context;
}
It's this line specifically: super(context, R.layout.shipments_out_row_item, data);
I thought it may be something to do with the way we are inserting the context into the adaptor and then changing the page before its finished but that proved inconclusive.
Paste bin with adaptor:Adaptor
The Fragment#getContext() is nullable. This method returns null when your fragment is detached from activity. The app crashes because you create the adapter while the fragment is not attached which results into a null passed to the constructor.
The method showShipments should only be called when the fragment is attached to the activity. There are callbacks onAttach() and onDetach() that will help you to detect the state. Also isAdded() returns you a boolean saying if the fragment is attached or not. Choose what is convenient for you.
Good luck!
Try refactor your adapter using BaseAdapter as follow
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
public class ShipmentsAdaptor extends BaseAdapter {
private RealmResults<Shipment> dataSet;
private Context mContext;
// View lookup cache
private static class ViewHolder {
TextView stockItemId;
TextView technicianName;
TextView shipmentDate;
}
public ShipmentsAdaptor(RealmResults<Shipment> dataSet, Context context) {
this.dataSet = dataSet;
this.mContext = context;
}
#Override
public int getCount() {
return dataSet.size();
}
#Override
public Object getItem(int position) {
return dataSet.get(position);
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// Get the data item for this position
Shipment shipment = (Shipment) getItem(position);
// Check if an existing view is being reused, otherwise inflate the view
ViewHolder viewHolder; // view lookup cache stored in tag
final View result;
if (convertView == null) {
viewHolder = new ViewHolder();
LayoutInflater inflater = LayoutInflater.from(mContext);
convertView = inflater.inflate(R.layout.shipments_out_row_item, parent, false);
viewHolder.stockItemId = convertView.findViewById(R.id.stockItemId);
viewHolder.technicianName = convertView.findViewById(R.id.technicianName);
viewHolder.shipmentDate = convertView.findViewById(R.id.shipmentDate);
result = convertView;
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
result=convertView;
}
lastPosition = position; //use getItemId() instead
if(shipment != null){
viewHolder.stockItemId.setText(String.valueOf(shipment.id));
if(shipment.technician != null){
viewHolder.technicianName.setText(shipment.technician.name);
}
viewHolder.shipmentDate.setText(shipment.shippingDate);
}
// Return the completed view to render on screen
return convertView;
}
}
Looks like you are calling fetchShipments(); before the fragment layout view (myView) is returned hence it is null when the adaptor is instantiated.
Try:
Move fetchShipments(); from onCreateView() and place it in onResume() or override onStart() and call it from there
You can check for null during your adapter setup to avoid this. In a Fragment, getActivity can sometimes return null at different points during the Fragment lifecycle. For example, in showShipments
Activity a = getActivity();
if( a == null || a.isFinishing() ) {
// Not in a valid state to show things anyway, so just stop and exit
return;
}
ShipmentsAdaptor adaptor = new ShipmentsAdaptor(savedShipments, a);
You can also check isAdded(), and if that is false you can get null from getActivity().
Also, consider moving the call to fetchShipments() from onCreateView to onActivityCreated instead.
Related
I am working through John Horton's Android Programming for Beginners, and am currently attempting to create a note-taking app. Horton has just introduced ListViews. However, I am having trouble with the adapter class:
public class NoteAdapter extends BaseAdapter {
List<Note> mNoteList = new ArrayList<Note>();
#Override
public int getCount(){
return mNoteList.size();
}
#Override
public Note getItem(int whichItem){
return mNoteList.get(whichItem);
}
#Override
public long getItemId(int whichItem){
return whichItem;
}
#Override
public View getView(int whichItem, View view, ViewGroup viewGroup){
// check if view has been inflated already
if (view == null){
LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); // ERROR HERE
view = inflater.inflate(R.layout.listitem, viewGroup, false);
}
return view;
}
}
The problem is in the getView method, where I'm attempting to inflate the layout: Android Studio throws an error: 'Cannot resolve getSystemService(java.lang.String)'.
As a complete newcomer just following through the book I have no idea where to go from here or what to try to resolve it - can anyone help?
The best way to get a LayoutInflater is by calling getLayoutInflater() on an Activity. That way, the activity's theme is taken into account. If NoteAdapter is defined inside of an Activity, just call getLayoutInflater(). If NoteAdapter is defined in its own separate Java class file, pass in a LayoutInflater via the constructor.
To more directly address your question, any View, like ListView, can call getContext() to get a Context. That is where getSystemService() is defined. So, replacing getSystemService() with viewGroup.getContext().getSystemService() would work.
You should pass Context to your adapter and then replace this line:
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
I hope this will help.
Use
view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.listitem, viewGroup,false);
Create a class variable and a Constructor for your adapter:
Context context;
public NoteAdapter(Context context){
this.context = context;
}
Then initialize the layoutinflater the following way:
LayoutInflater inflater = LayoutInflater.from(context);
Try
public class NoteAdapter extends BaseAdapter {
Context mContext = null;
public NoteAdapter(Context context){
mContext = context;
}
#Override
public View getView(int whichItem, View view, ViewGroup viewGroup){
// check if view has been inflated already
if (view == null){
LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); // ERROR HERE
view = inflater.inflate(R.layout.listitem, viewGroup, false);
}
return view;
}
}
First make the constructor of Adapter: like follow :
Context context;
public NoteAdapter(Context context)
{
this.context = context
}
Now use this context:
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
In my views, if you are learning then learn RecyclerView. bcz it is better than ListView. i am not saying that ListView has been depricated. But there alot of internal things in which RecyclerView is better.
Following is example of Adapter
public class NoteAdapter extends BaseAdapter {
List<Note> mNoteList = new ArrayList<Note>();
Context context;
public NoteAdapter(Context context){
this.context = context;
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
#Override
public int getCount(){
return mNoteList.size();
}
#Override
public Note getItem(int whichItem){
return mNoteList.get(whichItem);
}
#Override
public long getItemId(int whichItem){
return whichItem;
}
#Override
public View getView(int whichItem, View view, ViewGroup viewGroup){
// check if view has been inflated already
if (view == null){
view = inflater.inflate(R.layout.listitem, viewGroup, false);
}
return view;
}
}
Inside MainActivity.java
NoteAdapter noteA = new NoteAdapter(MainActivity.this);
OR
NoteAdapter noteA = new NoteAdapter(getContext());
OR
NoteAdapter noteA = new NoteAdapter(getActivity);
// if in Fragment
OR
NoteAdapter noteA = new NoteAdapter(getApplicationContext);
// will work but no need to use it. bcz this is context of whole application. For an adapter you don't need context of whole application.
mContext is Context which you pass to Custom Adapter
public boolean CheckInternet() {
ConnectivityManager connectivityManager = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
if (connectivityManager.getNetworkInfo(ConnectivityManager.TYPE_MOBILE).getState() == NetworkInfo.State.CONNECTED ||
connectivityManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI).getState() == NetworkInfo.State.CONNECTED) {
//we are connected to a network
return true;
}
return false;
}//end of check internet
I have a ViewPager with 3 fragments. The rightmost fragment has a ListView inside it. The problem is that on two weaker phones I tested this on, it works seemingly smooth and not laggy. However, when I test it on my Note 3, the transition from the middle fragment to this one is very laggy and over 300 frames are skipped according to the logcat. Also, if I lock the phone and then unlock it back onto the ListView it is very laggy to scroll or do anything, unless I swipe left twice to the leftmost fragment. This is my onCreateView, onAttach and onStart methods as well as the adapter below.
#Override
public View onCreateView(LayoutInflater inflater,ViewGroup container,Bundle savedInstanceState) {
View view= inflater.inflate(R.layout.fragment_c,container,false);
mainalyout = (LinearLayout) view.findViewById(R.id.linear_layout_listview);
listView = (ListView) view.findViewById(R.id.followed_cities);
horizontal_scroll = (HorizontalScrollView) view.findViewById(R.id.horizontal_scroll_view);
swipe = (LinearLayout) view.findViewById(R.id.scroll_up);
layout = (LinearLayout) view.findViewById(R.id.scroll_view_layout);
swipeRefreshLayout = (SwipeRefreshLayout) view.findViewById(R.id.swipe_refresh_layout);
return view;
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
context = MyApp.getContext();
session = new SessionManager(context);
userString = session.getUserDetails();
username = userString.get("username");
viewPager = (ViewPager) activity.findViewById(R.id.pic_pager);
parent = (FragmentActivityTesting) activity;
username = userString.get("username");
queue = Volley.newRequestQueue(context);
followed_cities = session.getFollowedCities();
try {
citysearcher = (citysearcher) activity;
} catch(Exception e) {}
try {
slideshowready = (slideshowready) activity;
}catch (Exception e) {}
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
}
#Override
public void onStart() {
super.onStart();
swipeRefreshLayout.setOnRefreshListener(this);
if (followed_cities.contains("")) {
followed_cities.clear();
}
if (getActivity().getIntent().getStringExtra("launcher").equals("add")) {
get_followed(username);
}
if (followed_cities.isEmpty()) {
followed_cities.add(new CityShort("","NONE"));
ArrayAdapter<CityShort> adapter = new EmptyAdapter();
adapterr = adapter;
listView.setAdapter(adapterr);
}
else {
ArrayAdapter<CityShort> adapter = new MyListAdapter();
adapterr = adapter;
listView.setAdapter(adapterr);
}
My adapter :
private class MyListAdapter extends ArrayAdapter<CityShort> {
public MyListAdapter() {
super(getActivity(), R.layout.followed_item, followed_cities);
}
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
itemView = convertView;
if (itemView == null) {
itemView = getActivity().getLayoutInflater().inflate(R.layout.followed_item, parent, false);
}
TextView city_name = (TextView) itemView.findViewById(R.id.followed_city_txt);
final String curr_city = followed_cities.get(position).getCityName();
city_name.setText(curr_city);
city_name.setTag(followed_cities.get(position).getCityId());
if (curr_city.length() > 15) {
city_name.setTextSize(TypedValue.COMPLEX_UNIT_SP, 18);
}
listView.setOnTouchListener(swipeDetector);
return itemView;
}
#Override
public int getViewTypeCount() {
return getCount();
}
#Override
public int getItemViewType(int position) {
return position;
}
}
I saw you had 2 listviews (findViewById(R.id.followed_cities) and findViewById(R.id.horizontal_scroll_view)). The horizontal listview is not well-implemented for recycling items.
Consider a better official support solution with RecyclerView. It supports both vertical and horizontal and provides better performance (as least the number of frames skipped will be less than current listview).
Two things that I see:
1) You have several LinearLayouts. I'm not certain what your xml layout looks like, but if LinearLayouts are nested they can certainly reduce visual rendering time. RelativeLayouts are more efficient(though I do use LinearLayouts when I'm first writing the xml file, because it's a little less time consuming for me as a general setup, then to later go back and update to using RelativeLayout).
2) You're doing all of these calculations on your MainThread, which is also Android's GPU essentially. You might be interested in processing all of this on a separate thread, this will certainly reduce rendering time. Check out "extends AsyncTask" and "Intent Service".
Change these things and try if works..
ViewHolder holder = null;
if (convertView == null) {
holder = new ViewHolder();
convertView = getActivity().getLayoutInflater().inflate(R.layout.followed_item, parent, false);
city_name = (TextView) itemView.findViewById(R.id.followed_city_txt); //Make this class level variable
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
Replace your adapter with this
package com.munk.gaanasync;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.view.LayoutInflater;
import java.util.zip.Inflater;
private class MyListAdapter extends ArrayAdapter<CityShort>
{
public MyListAdapter()
{
super(getActivity(), R.layout.followed_item, followed_cities);
mInflater = (LayoutInflater)getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
#Override
public View getView(final int position, View convertView, ViewGroup parent)
{
ViewHolder holder = null;
itemView = convertView;
if (itemView == null)
{
holder = new ViewHolder();
itemView = mInflater.inflate(R.layout.followed_item, parent, false);
holder.city_name = (TextView) itemView.findViewById(R.id.followed_city_txt);
itemView.setTag(holder);
}
else
{
holder = (Viewholder) itemView.getTag();
}
final String curr_city = followed_cities.get(position).getCityName();
holder.city_name.setText(curr_city);
holder.city_name.setTag(followed_cities.get(position).getCityId());
if (curr_city.length() > 15)
{
holder.city_name.setTextSize(TypedValue.COMPLEX_UNIT_SP, 18);
}
listView.setOnTouchListener(swipeDetector);
return itemView;
}
public static class ViewHolder
{
public TextView city_name;
}
#Override
public int getViewTypeCount()
{
return getCount();
}
#Override
public int getItemViewType(int position)
{
return position;
}
private Inflater mInflater;
}
My application gets all images URL from server and saves that to an ArrayList and displays these images in ViewPager. But it generates a IllegalStateException. Adapter given below:
public class FullScreenImageAdapter extends PagerAdapter {
private Context _activity;
private ArrayList<String> _imagePaths;
private LayoutInflater inflater;
// constructor
public FullScreenImageAdapter(Context activity,
ArrayList<String> imagePaths) {
this._activity = activity;
this._imagePaths = imagePaths;
}
#Override
public int getCount() {
return this._imagePaths.size();
}
#Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
ImageView imgDisplay;
inflater = (LayoutInflater) _activity
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View viewLayout = inflater.inflate(R.layout.item, container,
false);
imgDisplay = (ImageView) viewLayout.findViewById(R.id.cardImage);
Picasso.with(_activity).load(_imagePaths.get(position)).into(imgDisplay);
((ViewPager) container).addView(viewLayout, 0);
return viewLayout;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
((ViewPager) container).removeView((ImageView) object);
}
}
Adapter created as
FullScreenImageAdapter adapter=new FullScreenImageAdapter(FullScreenActivity.this,all_url);
viewPager.setAdapter(adapter);
And the log looks like below:
java.lang.IllegalStateException: The application's PagerAdapter
> changed the adapter's contents without calling
> PagerAdapter#notifyDataSetChanged! Expected adapter item count: 1,
> found: 3 Pager id: com.wat.clickzy:id/view_pager Pager class: class
> android.support.v4.view.ViewPager Problematic adapter: class
> com.wat.clickzy.FullScreenImageAdapter
Please help me
You need to call notifysetdatachanged on the adapter that you're using, every time you're adding/removing something to that adapter.
Look here for even more clarity.
I have searched multiple websites, android developer sites and after no luck to my issue had to seek the community help. All help and suggestion will be highly appreciated.
ISSUE :
I have in total 12 images that I receive from the server (Parse in here) and I show each of them in a PagerAdapter. If all the values are not null the adapter code works fine, the twist is I allow null values to be stored in server. When I get the whole list back from server, I just want to have those views in adapter which contains not null.
Example : Suppose 5 null paths I get then my adapter shows in total 12 views (7 with images, rest as blank pages).
My Adapter Code:
public class ProfileImageAdapter extends PagerAdapter implements OnTouchListener{
private Context localContext;
private LayoutInflater inflater;
private List<DataModel> parseObjects = new ArrayList<DataModel>();
private List<Integer> res = new ArrayList<Integer>();
// Declare Variables
private ImageView viewPagerDisplayImage;
/**
*
* #param context : The context where to display
* #param parseObjects : The ParseObject to work with
*/
public ProfileImageAdapter(Context context, List<DataModel> parseObjects) {
this.localContext = context;
this.parseObjects = parseObjects;
inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
#Override
public int getCount() {
return parseObjects.size();
}
#Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
#Override
public boolean isViewFromObject(View view, Object object) {
return (view == ((LinearLayout) object) && object != null);
}
/**
* What is the item to show
*/
#Override
public Object instantiateItem(ViewGroup container, int position) {
inflater = (LayoutInflater) localContext
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View itemView = inflater.inflate(R.layout.profile_image_item, container, false);
// Locate the Image View in viewpager_item.xml
viewPagerDisplayImage = (ImageView) itemView.findViewById(R.id.view_pager_display_image_view);
// Getting the image
try {
ParseFile imageFile = parseObjects.get(position).getImage();
if (imageFile != null) {
byte[] bitmapImageData = imageFile.getData();
Bitmap bitmap = BitmapFactory.decodeByteArray(bitmapImageData, 0, bitmapImageData.length);
viewPagerDisplayImage.setImageBitmap(bitmap);
viewPagerDisplayImage.setOnTouchListener(this);
// Add viewpager_item.xml to ViewPager
itemView.setTag("VALID");
((ViewPager) container).addView(itemView);
} else {
((ViewPager) container).addView(itemView);
itemView.setTag("INVALID");
destroyItem(container, position, itemView);
}
} catch (ParseException e) {
e.printStackTrace();
}
return itemView;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
// Remove viewpager_item.xml from ViewPager
//((ViewPager) container).invalidate();
((ViewPager) container).removeView((View) object);
}
}
My PagerActivity
public class PageViewerProfileImages extends Activity {
private ViewPager viewPager;
private ProfileImageAdapter profileImageAdapter;
private UserModel userModel = new UserModel();
// Create the Page View for the Profile Images
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.profile_pager);
List<DataModel> parseObjects = userModel.getDataModels();
viewPager = (ViewPager) findViewById(R.id.pagerProfile);
profileImageAdapter = new ProfileImageAdapter(this, parseObjects);
// Setting the adapter
viewPager.findViewWithTag("VALID");
viewPager.setAdapter(profileImageAdapter);
}
}
My references:
http://developer.android.com/reference/android/support/v4/view/PagerAdapter.html#instantiateItem(android.view.ViewGroup, int)
ViewPager PagerAdapter not updating the View
Create a condition where you parse your server response.
if(imageUrl!=null){
// write code to add image url in your list, which will you pass in your adapter.
}
After using it, it will pass in adapter only those value in adapter which have some value, then it will work.
You can place this condition in adapter when you pass your image-Url list and assign value in your adapter list.
then before assigning your value please check same condition then assign.
I have a custom list view adapter populated through an asynctask, I'm calling notifydatasetchanged in the onprogress function, and getCount() returns 10, yet my list never shows, Ive set a breakpoint and determined that getView() simply never is called. any ideas? Ive tried for hours and Im just stumped. Ive done the exactly same thing in another activity except that one used viewholders, this one only holds text based data so I didn't bother.
Adapter:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View row = convertView;
if(row == null) {
row = inflater.inflate(R.layout.podcastepisode, null);
}
PodcastItem item = items.get(position);
TextView episodeTitle = (TextView)row.findViewById(R.id.episodeTitle);
TextView episodeDate = (TextView)row.findViewById(R.id.episodeDate);
episodeTitle.setText(item.title);
episodeDate.setText(API.FormatPodcastDate(item.date));
return row;
}
My task:
protected void onProgressUpdate(PodcastItem... progress) {
AddPodcastActivity.episodes.add(progress[0]);
AddPodcastActivity.adapter.notifyDataSetChanged();
}
I'd recommend moving your list adapter from inside the Activity file to it's own file, and using something like this:
import java.util.ArrayList;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
public class EpisodeArrayAdapter extends BaseAdapter {
public EpisodeArrayAdapter(Context context) {
mContext = context;
items = new ArrayList<PodcastItem>();
}
private Context mContext;
private ArrayList<PodcastItem> items;
public void add(PodcastItem item) {
items.add(item);
notifyDataSetChanged();
}
public void remove(int index) {
items.remove(index);
notifyDataSetChanged();
}
public void clear() {
items.clear();
notifyDataSetChanged();
}
#Override
public int getCount() { return items.size(); }
#Override
public Object getItem(int position) { return items.get(position); }
#Override
public long getItemId(int position) { return position; }
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View row = convertView;
if(row == null) row = LayoutInflater.from(mContext).inflate(R.layout.podcastepisode, null);
PodcastItem item = items.get(position);
TextView episodeTitle = (TextView)row.findViewById(R.id.episodeTitle);
TextView episodeDate = (TextView)row.findViewById(R.id.episodeDate);
episodeTitle.setText(item.title);
episodeDate.setText(API.FormatPodcastDate(item.date));
return row;
}
}
This is the type of code we use for all the list adapters in Boid :) Also, notice that the add/remove/clear functions call notifyDataSetChanged(), which makes it so you don't have to call it yourself when adding items.
When you initialize it, you would just use:
EpisodeArrayAdapter adapter = new EpisodeArrayAdapter(this);
listView.setAdapter(adapter);
Adding items with the add() function will cause the list to update immediately. Make sure you call setAdapter for the list view that's using the adapter, otherwise there won't be any connection and nothing will show up in the list (didn't see a call to this in your code).