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

커스텀캘린더 뷰 add decorater로 인한 nullpoint오류에 대해 궁금합니다.

0 추천

커스텀 캘린더뷰를 작성중에 있는데, add decorater로 달력에 효과를 주는 것에 있어서 오류가 떠서 이렇게 작성해봅니다.

 1) add decorater로 인해 java.lang.NullPointerException: Attempt to invoke virtual method이런 오류가 뜹니다. 오류가 뜨는 부분은 아래 주석처리를 한 부분입니다. 어디에서 null이 나는 것인지 잘 모르겠습니다.ㅜㅜ

2) 데이터가 있을 때, 빨간점을 찍는 이벤트를 구현할 수 있는 함수가 어떤 함수인지 알 수 있을까요?

package com.hanium.android.maeumi.view.diary;


import android.app.Activity;
import android.database.Cursor;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;

import androidx.annotation.NonNull;

import com.hanium.android.maeumi.R;
import com.prolificinteractive.materialcalendarview.CalendarDay;
import com.prolificinteractive.materialcalendarview.CalendarMode;
import com.prolificinteractive.materialcalendarview.MaterialCalendarView;
import com.prolificinteractive.materialcalendarview.OnDateSelectedListener;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.concurrent.Executors;

//@Bind(R.id.calendarView)
//MaterialCalendarView materialCalendarView;

public class DiaryCalendarTest extends Activity {

    String time, kcal, menu;
    private final OneDayDecorator oneDayDecorator = new OneDayDecorator();
    Cursor cursor;
    MaterialCalendarView materialCalendarView;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_calendartest);

        //bind 대신 findViewById 이용
        MaterialCalendarView materialCalendarView = findViewById(R.id.calendarView);

        materialCalendarView.state().edit()
                .setFirstDayOfWeek(Calendar.SUNDAY)
                .setMinimumDate(CalendarDay.from(1900, 0, 1)) // 달력의 시작
                .setMaximumDate(CalendarDay.from(2900, 11, 31)) // 달력의 끝
                .setCalendarDisplayMode(CalendarMode.MONTHS)
                .commit();

        materialCalendarView.addDecorators(
                new SunDayDecorator(),  //토요일 색
                new SaturDayDecorator(),    //일요일 색
                oneDayDecorator);

        String[] result = {"2017,03,18", "2017,04,18", "2017,05,18", "2017,06,18"};

        new ApiSimulator(result).executeOnExecutor(Executors.newSingleThreadExecutor());

        //클릭 이벤트
        materialCalendarView.setOnDateChangedListener(new OnDateSelectedListener() {
            @Override
            public void onDateSelected(@NonNull MaterialCalendarView widget, @NonNull CalendarDay date, boolean selected) {
                int Year = date.getYear();
                int Month = date.getMonth() + 1;
                int Day = date.getDay();

                Log.i("Year test", Year + "");
                Log.i("Month test", Month + "");
                Log.i("Day test", Day + "");

                String shot_Day = Year + "," + Month + "," + Day;

                Log.i("shot_Day test", shot_Day + "");
                materialCalendarView.clearSelection();

                Toast.makeText(getApplicationContext(), shot_Day, Toast.LENGTH_SHORT).show();
            }
        });
    }


    private class ApiSimulator extends AsyncTask<Void, Void, List<CalendarDay>> {

        String[] Time_Result;

        ApiSimulator(String[] Time_Result) {
            this.Time_Result = Time_Result;
        }

        @Override
        protected List<CalendarDay> doInBackground(@NonNull Void... voids) {
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            Calendar calendar = Calendar.getInstance();
            ArrayList<CalendarDay> dates = new ArrayList<>();

            /*특정날짜 달력에 점표시해주는곳*/
            /*월은 0이 1월 년,일은 그대로*/
            //string 문자열인 Time_Result 을 받아와서 ,를 기준으로짜르고 string을 int 로 변환
            for (int i = 0; i < Time_Result.length; i++) {
                CalendarDay day = CalendarDay.from(calendar);
                String[] time = Time_Result[i].split(",");
                int year = Integer.parseInt(time[0]);
                int month = Integer.parseInt(time[1]);
                int dayy = Integer.parseInt(time[2]);

                dates.add(day);
                calendar.set(year, month - 1, dayy);
            }


            return dates;
        }
//        @Override
//        protected void onPostExecute(@NonNull List<CalendarDay> calendarDays) {
//            super.onPostExecute(calendarDays);
//
//            if (isFinishing()) {
//                return;
//
//                public void addDecorator(EventDecorator); {
//                    materialCalendarView.addDecorator(new EventDecorator(Color.GREEN, calendarDays, DiaryCalendarTest.this));
//                }
//            }
//        }
    }
}
답변부탁드리겠습니다 (120 포인트) 님이 2021년 6월 30일 질문

1개의 답변

0 추천

로그를 자세히 보시면 어느 오브젝트가 널인지 알 수 있습니다.  주석 부분의 addDecorator에서 에러가 난 다면 materialCalendarView나 calendarDay, DiaryCalendarTest 중의 하나이겠지요. 브레이크 포인트를 걸어넣고 디버깅을 해보셔도 금방 아실 수 있을 거예요.

수정:
 

Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_calendartest);
 
        //bind 대신 findViewById 이용
        MaterialCalendarView materialCalendarView = findViewById(R.id.calendarView);
    ...
}

onCreate에서 지역변수로 MaterialCalendarView를 생성하셨네요. AsyncTask의  asychronouns로 작업을 하기 때문에 apiSiumlator를 먼저 생성했다 하더라도 onPostExecute가 실행될 때는 onCreate는 이미 처리가 되고 난뒤면 materialCalendarView는 지역변수이므로 Null이 되겠죠.

추가로, 해당 코드에 대해 두가지 포인트만 말씀드릴게요.

먼저, AsyncTask는 안드로이드11부터 deprecated 되었습니다. 새로운 기능을 만드실 때는 사용하시면 안되는 클래스이구요, 기존에 사용 중인 곳에는  Thread를 직접 핸들링하시거나 API call의 경우 Retrofit같은 사용하셔서 대체하셔 합니다.

두번째는, AsyncTask를 사용하실 때 콜백의 위치인데요. 더 유연한 디자인은 AsyncTask는 callback를 받아서 callback을 호출만 하시고 직접 Activity에 있는 뷰에 접근하지 않는 것이 좋습니다. 당장 에러가 나는데도 AsyncTask의 문제인지 Activity 쪽의 문제인지 명확하지가 않잖아요. 자기의 책임이 아닌 것은 포함시키지 않는 것이 더 좋은 코드입니다. 즉 calendar를 업데이트하는 것은 AsyncTask의 일이 아니라 Activity쪽의 일입니다. 따라서 아랫처럼,

class ApiSimulator extends AsyncTask<Void, Void, List<CalendarDay>> {
       public interface Listener {
             void onSuccess(List<CalendarDay> calendarDays);
             void onError(Exception e);
       }
        
       private final String[] Time_Result;
       private ApiSimulator.Listener listener;
 
        public ApiSimulator(String[] Time_Result, ApiSimulator.Listener listener) {
            this.Time_Result = Time_Result;
            this.listener = listener;
        }

        public void removeListener() {
           listener = null
       }
 
        @Override
        protected List<CalendarDay> doInBackground(@NonNull Void... voids) {
            ...
        }

        @Override
        protected void onPostExecute(@NonNull List<CalendarDay> calendarDays) {
           super.onPostExecute(calendarDays);
           if (listener != null) {
               //성공이면 listener.onSuccess 에러이면 onError 호출.
           }
        } 
    }
}


public class DiaryCalendarTestActivity extends Activity, ApiSimuator.Listener {

     ...
     
   final ApiSumlator apiSimulator = new ApiSimulator(..., this);


   @Override
   public void onSuccess(List<CalendarDay> calendarDays) {

   }

  @Overrid
  public void onError(Exception e) {

  }
}

 

spark (226,420 포인트) 님이 2021년 7월 2일 답변
spark님이 2021년 7월 2일 수정
...