마스터Q&A 안드로이드는 안드로이드 개발자들의 질문과 답변을 위한 지식 커뮤니티 사이트입니다. 안드로이드펍에서 운영하고 있습니다. [사용법, 운영진]

setOnItemClickListener 질문입니다

0 추천
상세 데이터가 뜨도록 설계를 했는데 아예 클릭도 먹지 않고 Logcat도 뜨질 않네요... 혹시 제 코드에 문제가 있을까요? 있다면 어떤 식으로 고치면 될지 알려주시면 감사하겠습니다... 코드는 너무 길어 링크 첨부합니다!

https://www.evernote.com/shard/s453/sh/7c8ff3e0-b264-de6f-5537-a7ca2fcffbf3/e62b034283e0eecde3bb92f1f9c6824a
살려주세요제발제발요 (670 포인트) 님이 2022년 9월 30일 질문

3개의 답변

+1 추천
 
채택된 답변
public class SignNoticeItem implements Serializable {
    private final String id, title, content, date;

    public SignNoticeItem(String id, String title, String content, String date) {
        this.id = id;
        this.title = title;
        this.content = content;
        this.date = date;
    }

    public String getId() {
        return id;
    }

    public String getTitle() {
        return title;
    }

    public String getContent() {
        return content;
    }

    public String getDate() {
        return date;
    }
}

 

public class SignNoticeAdapter extends ArrayAdapter<SignNoticeItem> {
    private final LayoutInflater layoutInflater;
    private List<SignNoticeItem> items = new ArrayList<>();

    public SignNoticeAdapter(@NonNull Context context, List<SignNoticeItem> items) {
        super(context, R.layout.item_sign_notice, items);
        layoutInflater = LayoutInflater.from(context);
        this.items = items;
    }

    public void submitList(List<SignNoticeItem> items) {
        this.items = items;
        notifyDataSetChanged();
    }

    @NonNull
    @Override
    public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
        ViewHolder viewHolder;
        if (convertView == null) {
            convertView = layoutInflater.inflate(R.layout.item_sign_notice, parent, false);
            viewHolder = new ViewHolder(
                    convertView.findViewById(R.id.notice_num),
                    convertView.findViewById(R.id.notice_title)
            );
            convertView.setTag(viewHolder);
        } else {
            viewHolder = (ViewHolder) convertView.getTag();
        }

        viewHolder.bindItem(items.get(position));
        return convertView;
    }

    static class ViewHolder {
        private final TextView numText, titleText;

        public ViewHolder(TextView numText, TextView titleText) {
            this.numText = numText;
            this.titleText = titleText;
        }

        public void bindItem(SignNoticeItem item) {
            numText.setText(item.getId());
            titleText.setText(item.getTitle());
        }
    }
}
public class SignNoticeFragment extends Fragment {

    private ListView listView;
    private SignNoticeAdapter signNoticeAdapter;

    private static final String ENDPOINT = "http://capstudyapp.dothome.co.kr/example";

    public SignNoticeFragment() {
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_sign_notice, container, false);
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        ...

        listView.setOnItemClickListener((adapterView, view1, position, id) -> {
            goToSingNoticeDetails(position);
        });
    }

    private void goToSingNoticeDetails(int position) {
        Intent intent = new Intent(requireContext(), SignNoticeDetailsActivity.class);
        intent.putExtra(SignNoticeDetailsActivity.EXTRA_ITEM, signNoticeAdapter.getItem(position));
        startActivity(intent);
    }

    public void retrieveData() {
        ...
    }
}

 

public class SignNoticeDetailsActivity extends AppCompatActivity {

    public static final String EXTRA_ITEM = "SingNoticeItem";

    private TextView titleText;
    private TextView dateText;
    private TextView contentText;

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

        setupViews();
        bindItem();
    }

    private void setupViews() {
        titleText = findViewById(R.id.title_tv);
        contentText = findViewById(R.id.content_tv);
        dateText = findViewById(R.id.date_tv);
    }

    private void bindItem() {
        SignNoticeItem signNoticeItem = (SignNoticeItem) getIntent().getSerializableExtra(EXTRA_ITEM);
        titleText.setText(signNoticeItem.getTitle());
        contentText.setText(signNoticeItem.getContent());
        dateText.setText(signNoticeItem.getDate());
    }
}

아직 더 깔끔하게 할 수 있는 부분들이 좀 있지만, 이 정도로 해도 가독성과 코드 유지보수가 나아질 것 같습니다.

spark (224,800 포인트) 님이 2022년 9월 30일 답변
살려주세요제발제발요님이 2022년 10월 1일 채택됨
이렇게 상세하게 답해주실 줄은 몰랐는데 정말 감사합니다! 아직 어떤 코드가 깔끔한 코드인지 인지하지 못했는데 그 부분도 집어주셔서 감사해요 :)
+1 추천
모발폰이라 코드를 적을 수가 없네요

어댑터 아이템 클릭 처리가 이상해 보여요.
먼저 같은 액티비티를 왜 두번 여는지, 그리고 applicationContext 보다는 Activity.this를 사용하세요. 여기서 Activity는 현재 액티비티의 class명이예요. Context 객체는 scope과 밀접하게 관련되므로 적절한 scope을 가진 context를 넘기셔야 해요.

startActivity(new Intent(SignNotice.this, Detail.class)
.putExtra("position",position));
 }

참고로Detail은 AndroidMenifest.xml에 등록하셨나요?

Intent로 넘긴 position은 아래처럼 읽어오세요.
mIntent.getIntExtra("positon", -1)

아마도positon을 못받았다면 indePutOfBoundExeption 이 날 것 같습니다.
spark (224,800 포인트) 님이 2022년 9월 30일 답변
spark님이 2022년 9월 30일 수정
+1 추천

집에 예제 코드를 카피해서 확인해 봤는데, 언급하신 문제와는 관련이 없지만 고쳐야할 부분이 많이 있네요.

그리고 말씀하신 setOnItemClickListener는 문제없이 동작합니다. 클린빌드를 하시거나 앱을 지웠다 다시 설치해보세요.

코드 수정사항을 몇가지만 말씀드릴게요.

1 클래스, 메소드 등을 목적에 부합하는 알기쉽고 일반적으로 적용하는 이름으로 사용하세요.

SignNotice -> SignNoticeFragment
R.layout.activity_signnotice -> R.layout.fragment_sign_notice

SignNoticeList -> SignNoticeItem (List 가 아니라 ListAdapter의 Item임)
 R.layout.activity_sign_notice_list -> R.layout.item_sign_notice

Detail -> DetailActivity
 

public class SignNoticeItem {
    private final String id, title, content, date;

    public SignNoticeItem(String id, String title, String content, String date) {
        this.id = id;
        this.title = title;
        this.content = content;
        this.date = date;
    }

    public String getId() {
        return id;
    }

    public String getTitle() {
        return title;
    }

    public String getContent() {
        return content;
    }

    public String getDate() {
        return date;
    }
}

Java는 필드명, 메소드명에 underscore(_)를 사용하지 않고 camelCase를 사용합니다. 예, last_name -> lastName

2. SignNoticeFragment에 있는  listView, signeNoticeAdapter, url, signeNoticeList 등은 외부에 노출할 필요가 없기 때문에 private으로 만드세요.

3. signNoticeListArrayList를 Fragment의  public static멤버로 사용하는 것은 좋지않은 접근방법입니다. 앱이 백그라운드에 있다가 시스템에 의해 죽거나 하면 static이라할지라고 데이터가 사라집니다. 그리고 이런 데이터가 Fragment에 소속되어 있다는게 코듳 측면에서 자연스럽지 않습니다. 차라리 private List<SignNoticeItem> signNoticeList = new ArrayList<>()과 같이 멤버변수로 사용하고 SignNoticeItem 을  Serializable를 구현해서 DetailActivity로 넘기는게 낫습니다. 상태 등을 저장하는 static은 가급적 피하는게 좋고 public static의 경우는 더더욱 그렇습니다. 많이 사용할 수록 함부로 변경하거나 제거할 수 없기 때문에 추후에 문제가 생길 가능성이 높아집니다.

3. SignNoticeAdapter는 가능하면 RecyvlerView를 사용하는 것이 좋지만, ListAdpater를 사용하실거면 ViewHolder 패턴을 구현하여 뷰의 재사용을 할 수 있도록 해야 합니다. 그래야 데이터가 많아질수록 성능상으로 많은 차이가 나게 됩니다.

아래에 제가 언급한 부분을 수정한 코드를 올려 놓을 게요.

spark (224,800 포인트) 님이 2022년 9월 30일 답변
...