【问题标题】:NPE on clickListener in recyclerView in fragment when rotating Android app旋转Android应用程序时片段中recyclerView中clickListener上的NPE
【发布时间】:2018-07-22 14:32:29
【问题描述】:

我在片段中的 RecyclerView 上的 clickListener 上遇到 NPE 问题。最初它可以很好地显示一条吐司消息,但是当我旋转设备并触摸我得到的任何图标时:

java.lang.NullPointerException: Attempt to invoke interface method 'void com.example.android.newswiz.OnItemClickListener.onItemClick(java.lang.String)' on a null object reference
    at com.example.android.newswiz.Fragments.PublishersSlidePageFragment$PublishersAdapter$PublishersAdapterViewHolder$1.onClick(PublishersSlidePageFragment.java:86)

这是我的片段类。我已经评论了日志中有 NPE 的部分:

package com.example.android.newswiz.Fragments;

import android.content.Context;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import com.example.android.newswiz.OnItemClickListener;
import com.example.android.newswiz.R;


public class PublishersSlidePageFragment extends Fragment {

public OnItemClickListener listener;

public PublishersSlidePageFragment(){}

@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater,
                         @Nullable ViewGroup container, @Nullable Bundle 
savedInstanceState) {

    ViewGroup rootView = (ViewGroup) 
inflater.inflate(R.layout.fragment_publishers_slide_page,
            container, false);

    RecyclerView mRecyclerView = 
rootView.findViewById(R.id.recyclerView_publishers);
    int numberOfColumns = 
Integer.parseInt(getContext().getResources().getString(R.string.no_of_cols));
    mRecyclerView.setLayoutManager(new GridLayoutManager(getContext(), 
numberOfColumns));
    mRecyclerView.setHasFixedSize(true);
    PublishersAdapter mAdapter = new PublishersAdapter();
    mRecyclerView.setAdapter(mAdapter);
    return rootView;
}

public void setListener(OnItemClickListener onItemClickListener) {
        this.listener = onItemClickListener;
}

public  class PublishersAdapter extends RecyclerView.Adapter<PublishersAdapter.PublishersAdapterViewHolder>{


    @NonNull
    @Override
    public PublishersAdapter.PublishersAdapterViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        Context context = parent.getContext();
        LayoutInflater inflater = LayoutInflater.from(context);
        View view = inflater.inflate(R.layout.publisher_item, parent, false);
        return new PublishersAdapterViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull PublishersAdapter.PublishersAdapterViewHolder holder, int position) {
            holder.mPublisherImageView.setImageResource(mThumbIds[position]);
            holder.mPublisherTextView.setText(mPublishers[position]);
            holder.bind(mPublishers[position], listener);
    }

    @Override
    public int getItemCount() {
        return mThumbIds.length;
    }

    public class PublishersAdapterViewHolder extends RecyclerView.ViewHolder {

        private final ImageView mPublisherImageView;
        private final TextView mPublisherTextView;

        public PublishersAdapterViewHolder(View itemView) {
            super(itemView);
            mPublisherImageView = itemView.findViewById(R.id.publisher_icon);
            mPublisherTextView = itemView.findViewById(R.id.publisher_desc);
        }

        public void bind(final String mPublisher, final OnItemClickListener clickListener) {
            itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    clickListener.onItemClick(mPublisher); //NPE on this line
                }
            });
        }
    }
}

    // references to the images
    private Integer[] mThumbIds = {
            R.drawable.abc_news, R.drawable.abc_news_au,
            R.drawable.aftenposten, R.drawable.aljazeera_english,
            R.drawable.bbc, R.drawable.cbs_news,
            R.drawable.cnn_news, R.drawable.entertainment_weekly,
            R.drawable.espn, R.drawable.financial_post,
            R.drawable.financial_times, R.drawable.fox_news,
            R.drawable.fox_sports, R.drawable.ign,
            R.drawable.independent, R.drawable.lequipe,
            R.drawable.metro, R.drawable.msnbc,
            R.drawable.mtvnews, R.drawable.nat_geo,
            R.drawable.nbc_news, R.drawable.new_scientist,
            R.drawable.new_york_magazine, R.drawable.talk_sport,
            R.drawable.techradar, R.drawable.the_guardian,
            R.drawable.the_nyt, R.drawable.wsj
    };


    private String[] mPublishers = {"ABC News", "ABC News (AU)", "Aftenposten", "AlJazeera (ENG)"
            , "BBC", "CBS News", "CNN News", "Entertainment Weekly", "ESPN", "Financial Post",
            "Financial Times", "Fox News", "Fox Sports", "IGN", "Independent", "L'Equipe",
            "Metro", "MSNBC", "MTV News", "Nat. Geo.", "NBC News", "New Scientist",
            "NY Magazine", "Talk Sport", "TechRadar", "The Guardian", "NYT",
            "Wall Street Journal"};
}

Fragment 类和监听器在 Main Activity 中初始化:

public class MainActivity extends AppCompatActivity {


private ViewPager mPager;

private SectionsPageAdapter mSectionsPageAdapter;

private Context context;

private TabLayout tabLayout;

private OnItemClickListener publisherClickListener;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    context = getApplicationContext();

   // Login authentication code ....

    //Set up the ViewPager and PagerAdapter

        mSectionsPageAdapter = new 
SectionsPageAdapter(getSupportFragmentManager());
        mPager = findViewById(R.id.sources_container);
        setupViewPager(mPager);

        tabLayout = findViewById(R.id.tabs);
        tabLayout.setupWithViewPager(mPager);


}


//FIXME NPE on listener object if you rotate screen and touch icon

private void setupViewPager(ViewPager mPager) {

    SectionsPageAdapter adapter = new SectionsPageAdapter(getSupportFragmentManager());
    PublishersSlidePageFragment publishersSlidePageFragment = new PublishersSlidePageFragment();

    publisherClickListener = new OnItemClickListener() {
        @Override
        public void onItemClick(String item) {
            Toast.makeText(context, "You have selected " + item, Toast.LENGTH_SHORT).show();
        }
    };

    publishersSlidePageFragment.setListener(publisherClickListener);

    adapter.addFragment(publishersSlidePageFragment, "Publishers");
    adapter.addFragment(new CategoriesSlidePageFragment(), "Categories");
    adapter.addFragment(new CountriesSlidePageFragment(), "Countries");
    mPager.setAdapter(adapter);
}

我觉得很奇怪,当屏幕旋转时重新创建片段是可以的,但无法为片段创建新的 clickListener。我搜索了 StackOverflow 并发现 Save interface (Listener) in onSaveInstanceState 很有用,但我认为在我的情况下保存听众不是一个好习惯。我想创建一个新的侦听器并将其设置在片段内的 Recycler View 上。我可能遗漏了一些琐碎的事情,但有人可以建议吗?

【问题讨论】:

    标签: android-fragments android-recyclerview onitemclicklistener


    【解决方案1】:

    我没有足够的代表发表评论,所以我会提供我认为可能的答案...

    首先,我注意到没有对传递给方法“bind(final String mPublisher, final OnItemClickListener clickListener)”的视图“itemView”的引用,而且我看不到它全局可用。不知道为什么它会在屏幕旋转后运行一次和 NPE。

    所以,这指向我不保存片段实例。当你旋转你的设备时,有些东西被破坏了,如果日志是正确的,那就是变量 mPublisher 被作为 null 传入。

    mPublishers 在第一次创建片段时存在,但看起来它正在被销毁/没有在方向更改时重新创建。

    我要尝试的第一件事是 setRetainInstance(true) 在配置更改时保存片段实例。我一直在 onCreate() 中设置它,但我看不出你不能在 onCreateView() 中设置它。

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        if (DEBUG) Log.i(TAG, "onCreate(Bundle)");
        super.onCreate(savedInstanceState);
    
        // retain this fragment across configuration changes
        setRetainInstance(true);
    }
    

    如果这不会自动为您完成,请在重新创建片段时保存发布者列表。这是在片段的 onSaveInstanceState() 中完成的。

    @Override
    public void onSaveInstanceState(@NonNull Bundle bundle) {
        if (DEBUG) Log.i(TAG, "onSaveInstanceState(Bundle)");
        // make sure to call the super method so that the states of our views are saved
        super.onSaveInstanceState(bundle);
    
        // save our own state variables
        bundle.putStringArray("publishers", mPublishers);
    }
    

    然后你可以在你的 onCreateView() 方法中恢复它们。

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    
        ViewGroup rootView = (ViewGroup) inflater.inflate(R.layout.fragment_publishers_slide_page, container, false);
    
        RecyclerView mRecyclerView = rootView.findViewById(R.id.recyclerView_publishers);
        int numberOfColumns = Integer.parseInt(getContext().getResources().getString(R.string.no_of_cols));
        mRecyclerView.setLayoutManager(new GridLayoutManager(getContext(), numberOfColumns));
        mRecyclerView.setHasFixedSize(true);
        PublishersAdapter mAdapter = new PublishersAdapter();
        mRecyclerView.setAdapter(mAdapter);
    
        // if we have a savedInstanceState, restore the Fragment
        if (savedInstanceState != null) {
            mPublishers = savedInstanceState.getStringArray("publishers");
        }
    
        return rootView;
    }
    

    您可能还必须对其他变量执行此操作,例如您的 mThumbIds 数组。

    【讨论】:

      【解决方案2】:

      解决方案实际上是一个简单的修复 - 我实际上需要在 Fragment 类中定义一个 OnItemClickListener 接口。然后重写 onAttach 方法将侦听器设置为上下文。以前我只是做了一个单独的界面。

      所以在 Fragment 类中:

      public class PublishersSlidePageFragment extends Fragment {
      
      private OnItemClickListener listener;
      
      public PublishersSlidePageFragment(){}
      
      public interface OnItemClickListener{
          void onItemClick(String item);
      }
      
      @Override
      public void onAttach(Context context) {
          super.onAttach(context);
          listener = (OnItemClickListener) context;
      }
      // ... rest of code
      

      然后在MainActivity中,声明你将实现PublishersSlidePageFragment.OnItemClickListener:

      public class MainActivity extends AppCompatActivity implements PublishersSlidePageFragment.OnItemClickListener {
      

      那么那个类需要实现监听方法:

      @Override
      public void onItemClick(String item) {
          Toast.makeText(context, "You have clicked " + item, Toast.LENGTH_SHORT).show();
      }
      

      【讨论】:

        猜你喜欢
        • 2014-07-25
        • 1970-01-01
        • 2023-03-18
        • 2020-05-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多