MIS.W 公式ブログ

早稲田大学公認、情報系創作サークル「早稲田大学経営情報学会」(MIS.W)の公式ブログです!

Java+sqlite+BroadcastReceiver+AlarmManagerでToDoアプリを作ってみよう【新歓ブログリレー2020 10日目】

こちらはMIS.W新歓ブログ10日目の記事となります。

はじめに

みなさんこんにちは。54代の白塗りの高級フクロウHarrsionKawagoe(@hrsnkwge_pro)です。 自分はみすではプロ研として活動しており、趣味と仕事としてWebアプリやAndroidアプリを作ったりします。 今回は、Java&sqlite&BroadcastReceiver&AlarmManagerを使ったAndroid版Todoアプリの作り方を紹介したいと思います。(タイトルが文字制限を超えてしまったようですね…申し訳ございません…)

完成品

まず完成品をみてみましょう:

f:id:HarrisonKawagoe8111F:20200405144305p:plain f:id:HarrisonKawagoe8111F:20200405144321p:plain f:id:HarrisonKawagoe8111F:20200405144335p:plain

タスクの新規作成、編集、削除機能以外に、指定した時間になったら自動的に通知を出す機能も入っています。 また、タスクは状態によって3つの色に分かれています:

赤:指定した時間を過ぎた

黄:指定した時間まであと10分弱

水:上記以外

目標物を確認できたら、早速手を動かして実際の開発に入ってみましょう!

1.Sqliteを使ったデータの永続化

まずタスクデータをデバイスストレージに保存する機能を作ります。 Androidの場合、データをデバイスストレージに保存するには、SqliteかShared Preferecesを使う必要があります。 一つ一つのタスクデータを管理する場合、このように、テーブルで管理したほうがやりやすそうですね。

f:id:HarrisonKawagoe8111F:20200405150714p:plain

Androidバイス上でExcelみたいにテーブルでデータを管理する時は、Sqliteを使います。 アプリからSqliteデータベースにアクセスするには、Sqliteのヘルパークラスを作成する必要があります。 プロジェクトフォルダーからJavaクラスを新しく作成し、下記のように記述します。

public class DBHelper  extends SQLiteOpenHelper {

    private static final int DATABASE_VERSION = 1;


    private static final String DATABASE_NAME = "TodoDB.db";    // データーベースのファイル名
    private static final String TABLE_NAME = "tododb"; // テーブル名

    DBHelper(Context context){
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        //.dbファイルが存在しなければ作成する
        db.execSQL(
               "CREATE TABLE " + TABLE_NAME +" (id INTEGER PRIMARY KEY, title TEXT,content TEXT, time INTEGER)"
        );
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        //.dbファイルのアップグレードを行うメソッド
        db.execSQL(
                "DROP TABLE IF EXISTS " + TABLE_NAME
        );
    }


}

この様にヘルパーを作成したら、アプリからデータベースにアクセスできるようになり、アプリを閉じてもデータが保持されます。

2.データを追加・更新・削除するメソッドの作成

次に、データベースにデータを追加、編集、削除する機能を作ります。 MainActivity.javaで、下記の様に記述します:

 public static DBHelper helper;
    public static SQLiteDatabase db;
    public static Context context;
 public static DBlistAdapter dBlistAdapter;

    public static void insertData(SQLiteDatabase db,String title,String com, long time){
        //データの追加
        ContentValues values = new ContentValues();
        values.put("title",title);
        values.put("content", com);
        values.put("time", time);

        db.insert("tododb", null, values);
    }

    public static void updateData(SQLiteDatabase db, int id, String title,String com, long time){
        //データの更新
        ContentValues values = new ContentValues();
        values.put("title",title);
        values.put("content", com);
        values.put("time", time);

        db.update("tododb", values, "id = "+id, null);
    }

    public static void deleteData(SQLiteDatabase db, int id){
        //データの削除
        db.delete("tododb","id = "+id,null);
    }

データを更新、削除する時は、タスクデータのidを渡します。 これらのメソッドと変数は、後ほどタスクの作成と編集を行うクラスでも使いますので、全部Static修飾子をつけます。 Static修飾子をつけると、MainActivityクラスで宣言した変数とメソッドが他のクラスでも使えるようになります。

3.タスクリストを表示するBaseAdapterの作成

f:id:HarrisonKawagoe8111F:20200405144305p:plain

データベースに保存されているデータをこのようにリスト状に表示するには、ListViewコンポーネントを使う必要があります。 データベースの内容をそのままListViewに使うことはできないので、データを一旦ArrayListに保存し、ArrayListからListViewにデータを渡す方法を採用します(効率悪いけど)。 そこで、ArrayListからListViewにデータを渡す役割を果たしているのは、BaseAdapterクラスです。 まず、データベースのデータをArrayListのオブジェクトに変換するDataChildクラスを作成します:

public class DataChild{
    private String title;
    private String content;
    private Long time;
    private int id;

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public Long getTime() {
        return time;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public void setTime(Long time) {
        this.time = time;
    }


    DataChild(int id,String title,String content,Long time){
        setId(id);
        setTitle(title);
        setContent(content);
        setTime(time);
    }
}

次に、MainActiviy.javaで、ListViewとBaseAdapterに使うArrayListを宣言します:

  public static List datas;

datasは初期化しないと使えないので、getDatasメソッドを作成し、データベースの中に入ってる全てのデータをdatasに入れます:

if(helper == null){
            helper = new DBHelper(context);
        }

        if(db == null){
            db = helper.getReadableDatabase();
        }
        if(datas == null){
           datas = new ArrayList<DataChild>();
        }
        if(dBlistAdapter == null){
            dBlistAdapter = new DBlistAdapter(context);
        }
        datas.clear();
        Cursor cursor = db.query(
                "tododb",
                new String[] { "id","title","content", "time" },
                null,
                null,
                null,
                null,
                "time"
        );

        cursor.moveToFirst();
        Log.d("size",cursor.getCount()+"");
        for(int i = 0;i <cursor.getCount();i++){
            Log.d("tag",cursor.getString(0)+":"+cursor.getString(1));
            DataChild dh = new DataChild(cursor.getInt(0),cursor.getString(1),cursor.getString(2),cursor.getLong(3));
            datas.add(dh);
            cursor.moveToNext();
        }
        cursor.close();
    }

Cursorクラスを使うと、データベースのテーブルの中に入ってるデータを取り出すことができます。 Cursorを使ってテーブル内の要素を全てDataChildクラスに変換し、datasに変換後のデータを渡します。 これで、datasの初期化が完了しましたので、BaseAdapterクラスを作成していきたいと思います。

public class DBlistAdapter extends BaseAdapter {

    private LayoutInflater inflater;
    private Context context;
    public DBlistAdapter(Context context) {
        super();
        this.context = context;
        this.inflater = LayoutInflater.from(context);
    }

    @Override
    public int getCount() {
        return MainActivity.datas.size();
    }

    @Override
    public Object getItem(int position) {
        return position;
    }

    @Override
    public View getView(final int position, View convertView, ViewGroup parent) {
        if (convertView == null) {
            convertView = this.inflater.inflate(R.layout.list_child, null);
        }
        LinearLayout linearLayout = convertView.findViewById(R.id.container);
        DataChild dc = (DataChild) MainActivity.datas.get(position);
        TextView title = convertView.findViewById(R.id.title);
        title.setText(dc.getTitle());
        TextView time = convertView.findViewById(R.id.time);
        Long milltime = dc.getTime();
        Date today = new Date();
  //タスクの状態によって背景の色を変える
        if(milltime-today.getTime()<=600000&&milltime>=today.getTime()){
            linearLayout.setBackgroundColor(Color.YELLOW);
        }else if(milltime<today.getTime()){
            linearLayout.setBackgroundColor(Color.RED);
        }else{
            linearLayout.setBackgroundColor(Color.CYAN);
        }
        Date d = new Date(milltime);
        time.setText(new SimpleDateFormat("yyyy-MM-dd HH:mm").format(d));

        convertView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                
            }
        });
        return convertView;
    }

    @Override
    public long getItemId(int position) {
        return position;
    }
}

クラス名をDBlistAdapterにしました。 要素のUIへの操作はgetViewメソッド内で行います。

A「あのさぁ…エラー吐いてるゾ…」

ワイ「マ?どんなエラーが出たの?」

A「こんな感じ:

f:id:HarrisonKawagoe8111F:20200405191500p:plain

ワイ「あっ…(察し)」

すいませんお許しください…タスクリストの要素のUIの作成を忘れました… というわけでlayoutフォルダーでlist_child.xmlを作成します:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="200px"
    android:id="@+id/container"
    android:padding="10px">
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="50px"
        android:text="Title"
        android:id="@+id/title"></TextView>'
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="time"
        android:id="@+id/time"></TextView>

</LinearLayout>

Androidの場合、基本的にUIの作成にはXMLを使います。 一応、JavaではUIのコンポーネントを消したり、位置を変えたりすることもできますが、JavaでUIを一から作成するのはマジで面倒なのでオススメしません。 これでDBlistAdapterのエラーが消えるはずです。 BaseAdapterの作成が終わったので、メイン画面のListViewのアダプターにDBlistAdapterを指定すると、データベース内のデータがListViewに反映されます。

4.メイン画面の作成

layoutフォルダーの中に入ってるactivity_main.xmlの内容をこのように編集します:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <ListView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/todolist"
        android:background="#c0c0c0"
        android:layout_marginBottom="150px"></ListView>

    <Button
        android:id="@+id/addtodo"
        android:layout_width="match_parent"
        android:layout_height="150px"
        android:layout_alignParentBottom="true"
        android:background="#00ff00"
        android:text="+"
        android:textSize="100px"></Button>

</RelativeLayout>

Designタブを押すと、このようなUIを確認することができます: f:id:HarrisonKawagoe8111F:20200405193124p:plain

次に、MainAcitivity.javaでボタンをクリックした後のイベントの作成します:

    public static ListView listView;

   @RequiresApi(api = Build.VERSION_CODES.O)
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        context = getApplicationContext();
        setContentView(R.layout.activity_main);
        listView = findViewById(R.id.todolist);
        getData();//先ほど作成したgetDataメソッドを呼び出して、ArrayListの初期化を行う
        Button addtodo = findViewById(R.id.addtodo);
        addtodo.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
               //addtodoボタンを押された後に発生するイベント
            }
        });
    }

DBlistAdapterとListviewとの連携はgetDataメソッド内に行います:

listView.setAdapter(dBlistAdapter);
dBlistAdapter.notifyDataSetChanged();

上記のコードをcursor.close();の前に追加しましょう。 これで、getDataメソッドは、ArrayListとDBAdapterの初期化だけではなく、データベース内のデータが変更されたとしても、getDataメソッドを呼び出すだけでArrayListとDBAdapterの更新ができます。 現在の段階でアプリを実行してみると、メイン画面を確認できると思います。 データベース内のデータも、メイン画面に反映されます。

5.タスクの作成&編集画面の作成

コードの無駄をしたくないので(お前が言うな)、編集画面と作成画面のレイアウトファイルを一つにまとめます。 layoutフォルダーにeditschedule.xmlを追加し、下記のように記述します:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#c0c0c0">
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="50px"
        android:text="タイトル"></TextView>

    <EditText
        android:id="@+id/edittitle"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"></EditText>
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="50px"
        android:text="日付"></TextView>
    <Button
        android:layout_width="match_parent"
        android:layout_height="150px"
        android:text="日付を選択してください"
        android:id="@+id/editdate"></Button>
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="50px"
        android:text="時間"></TextView>
    <Button
        android:layout_width="match_parent"
        android:layout_height="150px"
        android:text="時間を選択してください"
        android:id="@+id/edittime"></Button>
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="50px"
        android:text="内容"></TextView>
    <EditText
        android:layout_width="match_parent"
        android:layout_height="400px"
        android:lines="114514"
        android:id="@+id/editcontent"></EditText>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <Button
            android:layout_weight="1"
            android:layout_width="match_parent"
            android:layout_height="150px"
            android:text="戻る"
            android:background="#ffffff"
            android:id="@+id/back"></Button>
        <Button
            android:layout_weight="1"
            android:layout_width="match_parent"
            android:layout_height="150px"
            android:text="保存"
            android:background="#00ff00"
            android:id="@+id/save"></Button>
        <Button
            android:layout_weight="1"
            android:layout_width="match_parent"
            android:layout_height="150px"
            android:text="削除"
            android:background="#ff0000"
            android:id="@+id/delete"></Button>

    </LinearLayout>

</LinearLayout>

次に、タスクの編集&作成画面にタッチイベントを追加したいので、EditScheduleクラスを作成します。

public class EditSchedule extends AppCompatActivity implements
        View.OnClickListener {

    private int mYear, mMonth, mDay, mHour, mMinute;
    Button Editdate,Edittime,delete,save,back;
    EditText title,content;
    static int index;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.editschedule);
        delete = findViewById(R.id.delete);
        save = findViewById(R.id.save);
        back = findViewById(R.id.back);
        back.setOnClickListener(this);
        title = findViewById(R.id.edittitle);
        content = findViewById(R.id.editcontent);
        Editdate = findViewById(R.id.editdate);
        Editdate.setOnClickListener(this);
        Edittime = findViewById(R.id.edittime);
        Edittime.setOnClickListener(this);
        save.setOnClickListener(this);
        delete.setOnClickListener(this);

        if(getIntent().getStringExtra("flag").equals("edit")){
   //タスクを編集する場合、ID以外の情報を全部反映する
            index = Integer.valueOf(getIntent().getStringExtra("index"));
            int id = ((DataChild)MainActivity.datas.get(index)).getId();
            DataChild dc = (DataChild) MainActivity.datas.get(index);
            Long milltime = dc.getTime();
            Date d = new Date(milltime);
            title.setText(dc.getTitle());
            content.setText(dc.getContent());
            Editdate.setText(new SimpleDateFormat("yyyy-MM-dd").format(d));
            Edittime.setText(new SimpleDateFormat("HH:mm").format(d));

        }else{
            delete.setVisibility(View.INVISIBLE);//タスクを新しく作成する場合、削除操作を行うとNullPointerException例外が発生するので、削除ボタンを隠す

        }
    }


    @Override
    public void onClick(View v) {
        if(v == Editdate){
            final Calendar c = Calendar.getInstance();
            mYear = c.get(Calendar.YEAR);
            mMonth = c.get(Calendar.MONTH);
            mDay = c.get(Calendar.DAY_OF_MONTH);

   //日付の設定
            DatePickerDialog datePickerDialog = new DatePickerDialog(this,
                    new DatePickerDialog.OnDateSetListener() {

                        @Override
                        public void onDateSet(DatePicker view, int year,
                                              int monthOfYear, int dayOfMonth) {
                            String month = String.valueOf(monthOfYear+1);
                            if(monthOfYear+1<10&&month.length()==1){
                                month = "0" + month;
                            }
                            String day = String.valueOf(dayOfMonth);
                            if(dayOfMonth<10&&day.length()==1){
                                day = "0" + day;
                            }


                            Editdate.setText(year+"-"+month+ "-" + day);

                        }
                    }, mYear, mMonth, mDay);
            datePickerDialog.show();
        }else if(v == Edittime){
   //時間の設定
            final Calendar c = Calendar.getInstance();
            mHour = c.get(Calendar.HOUR_OF_DAY);
            mMinute = c.get(Calendar.MINUTE);

            TimePickerDialog timePickerDialog = new TimePickerDialog(this,
                    new TimePickerDialog.OnTimeSetListener() {

                        @Override
                        public void onTimeSet(TimePicker view, int hourOfDay,
                                              int minute) {
                            String hour = String.valueOf(hourOfDay);
                            if(hourOfDay<10&&hour.length()==1){
                                hour = "0" + hour;
                            }
                            String minutes = String.valueOf(minute);
                            if(minute<10&&minutes.length()==1){
                                minutes = "0" + minutes;
                            }

                            Edittime.setText(hour + ":" + minutes);
                        }
                    }, mHour, mMinute, false);
            timePickerDialog.show();

        }else if(v == back){
            finish();
        }else if(v == save){
   //タスクの保存
            String timetemp = Editdate.getText().toString() + " " + Edittime.getText().toString();
            SimpleDateFormat sdfDate = new SimpleDateFormat("yyyy-MM-dd HH:mm");
            Long mills = 0L;
            try{
                Date newDate = sdfDate.parse(timetemp);
                mills = newDate.getTime();
            }catch (Exception e) {

            }
            if(getIntent().getStringExtra("flag").equals("edit")){
                int id = ((DataChild)MainActivity.datas.get(index)).getId();
                MainActivity.updateData(MainActivity.db,id,title.getText().toString(),content.getText().toString(),mills);
            }else{
                MainActivity.insertData(MainActivity.db,title.getText().toString(),content.getText().toString(),mills);
            }
            Toast.makeText(getApplicationContext(),"保存しました!",Toast.LENGTH_LONG).show();
            MainActivity.getData();
            finish();

        }else if(v==delete){
   //タスクの削除
            int id = ((DataChild)MainActivity.datas.get(index)).getId();
            MainActivity.deleteData(MainActivity.db,id);
            Toast.makeText(getApplicationContext(),"削除しました!",Toast.LENGTH_LONG).show();
            MainActivity.getData();
            finish();
        }
    }



}

メイン画面からタスク編集画面に遷移する際にflagパラメータが渡されます。 ここでは、flagの値を使って編集モードを判断しています。 また、時間と日付の設定にはそれぞれTimePickerDialogとDatePickerDialogが使われています。 ここで注意すべきな点がありますが、匿名クラス内に匿名クラスを宣言することはできません。 TimePickerDialogとDatePickerDialogの設定には既に匿名クラスが使われているので、この場合、クリックイベントを匿名クラスとして追加するとエラーが吐きます。 したがって、EditScheduleクラスの中でView.OnClickListenerを実装する必要があります。 extends AppCompatActivityの後ろに implementsView.OnClickListenerを追加すると、onClickメソッドをオーバライドすることが可能になり、そこでクリックイベントを管理します。

Activityを新しく作った場合、AndroidManifest.xmlでActivityを宣言する必要があるので、下記のコードを追加します:

 <activity android:name=".EditSchedule"></activity>

6.画面遷移イベントの追加

先ほど作成したメイン画面の「追加」ボタンとListViewの要素に画面遷移を行うクリックイベントを追加します。 一つのActivityから別のActivityに移動する際は、Intentクラスを使います。 putExtraメソッドを使うと、移動する同時にパラメータを渡すことができます。 MainActivityのaddtodoボタンに下記のようにクリックイベントを追加します:

addtodo.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this,EditSchedule.class);
                intent.putExtra("flag","new");
                startActivity(intent);
            }
        });

ListViewの要素をタッチすると編集画面に移動するイベントを追加したいので、DBlistAdapterの convertViewのsetOnClickListenerに下記のコードを追加します:

Intent intent = new Intent(context,EditSchedule.class);
intent.putExtra("flag","edit");
intent.putExtra("index",String.valueOf(position));
context.startActivity(intent);

ここまできたら、プロジェクトをビルドしてみましょう。 もし問題がなければ、通知機能以外の機能は全て使えるようになると思います。

7.通知機能の作成

一定時間後に処理を行う時にはBroadcastReceiverを使います。 BroadcastReceiverに追加したタスクは、例えアプリのプロセスが消されたとしても、時間になったら自動的に実行されますので、アラームアプリではよく使われています。 通知を作成する時には Notificationクラスを使います。 ただ、Android Oreo以上のバージョンでは、通知をプッシュする前にNotification Channelを作成する必要がありますので、Build.VERSION.SDK_INTでAndroidのバージョンを判断する必要があります。

まず、ReminderBroadcastクラスを作成します:

public class ReminderBroadcast extends BroadcastReceiver {



    @Override
    public void onReceive(Context context, Intent intent) {
        Intent notificationIntent = new Intent(context, MainActivity.class);
        PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, notificationIntent, 0);

        if(dBlistAdapter != null){
            dBlistAdapter.notifyDataSetChanged();
        }

        Notification notification;
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
           notification = new Notification.Builder(context,"channel_1")//AndroidのバージョンがOreoより新しい場合、作成した通知チャンネルのIDをNotificationのコンストラクタに渡す
                    .setContentTitle(intent.getStringExtra("title"))
                    .setContentText(intent.getStringExtra("content"))
                    .setSmallIcon(R.drawable.ic_launcher_background)
                    .setContentIntent(pendingIntent)//通知をタッチした後に開く画面
                    .setVisibility(Notification.VISIBILITY_PUBLIC)
                    .build();
        }else{
            notification = new Notification.Builder(context)
                    .setContentTitle(intent.getStringExtra("title"))
                    .setContentText(intent.getStringExtra("content"))
                    .setSmallIcon(R.drawable.ic_launcher_background)
                    .setContentIntent(pendingIntent)//通知をタッチした後に開く画面
                    .setVisibility(Notification.VISIBILITY_PUBLIC)
                    .build();
        }
        NotificationManager notificationManager = (NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE);
        notificationManager.notify(1, notification);

    }
}

ついでに、ReminderBroadcastをAndroidManifest.xmlでReceiverクラスとして宣言する必要があります。

<receiver android:name=".ReminderBroadcast">
</receiver>

次に、MainActivityクラスで、Notification Channelの宣言と初期化を行います:

   
    public static AlarmManager alarmManager;
    public static NotificationManager manager;
    public static NotificationChannel channel;
 
    @RequiresApi(api = Build.VERSION_CODES.O)
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        context = getApplicationContext();
        alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
        setContentView(R.layout.activity_main);
        listView = findViewById(R.id.todolist);
        manager = (NotificationManager)
                getSystemService(Context.NOTIFICATION_SERVICE);
        channel = new NotificationChannel(
                "channel_1",
                "89319",
                NotificationManager.IMPORTANCE_DEFAULT
        );
        channel.setLightColor(Color.GREEN);
        channel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
        manager.createNotificationChannel(channel);

        getData();
        Button addtodo = findViewById(R.id.addtodo);
        addtodo.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this,EditSchedule.class);
                intent.putExtra("flag","new");
                startActivity(intent);
            }
        });
    }

タスクをBroadcastReceiverに実行させるには、AlarmManagerでタスクを予約する必要があります。 getDataのdatas.add(dh);の前に通知の予約機能を追加します:

            Intent intentbefore = new Intent(context, ReminderBroadcast.class);
            PendingIntent pendingIntentbefore = PendingIntent.getBroadcast(context, dh.getId(), intentbefore, 0);
            pendingIntentbefore.cancel();
            alarmManager.cancel(pendingIntentbefore);
            long today = (new Date()).getTime();
            long after = dh.getTime();
            Date date = new Date(after);
            String notificationtitle = new SimpleDateFormat("hh:mm").format(date);
            Intent intent = new Intent(context,ReminderBroadcast.class);
            intent.putExtra("id",String.valueOf(dh.getId()));
            intent.putExtra("title",notificationtitle);
            intent.putExtra("content",dh.getTitle());
            PendingIntent pendingIntent = PendingIntent.getBroadcast(context,dh.getId(),intent,PendingIntent.FLAG_ONE_SHOT);
            AlarmManager alarmManager = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
            if(today<after){
                alarmManager.set(AlarmManager.RTC_WAKEUP,after,pendingIntent);
            }

同じ通知を何回もプッシュされたくないので、予約する前に同じIDの通知がプッシュされたかどうかをチェックします。 次に、タスクの削除ボタンのイベントに通知を取り消し処理を追加します:

            Intent intent = new Intent(context, ReminderBroadcast.class);
            PendingIntent pendingIntent = PendingIntent.getBroadcast(context, id, intent, 0);
            pendingIntent.cancel();
            MainActivity.alarmManager.cancel(pendingIntent);

これでToDoアプリは完成です。ここまで見てくれた皆さん、お疲れ様でした!

8.(おまけ)アラームアプリっぽくしてみた

7の続きなんですが、BroadcastReceiverのonReceiveの中にIntentをうまく使うと、アラームアプリみたいに指定した時間にアプリを起動することができます。 ただ、この場合、IntentのコンストラクタでActivity名を指定するのではなく、setClassNameにパッケージ名と遷移先クラスの名前を正しく渡す必要があります

Intent alarmIntent = new Intent();
alarmIntent.setClassName(context.getPackageName(),AlarmActivity.class.getName());
alarmIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(alarmIntent);

このように書くと指定された時間になったらAlarmActivityが開きます。

9.おわり

いかがでしょうか? 初心者にとってはちょっと難しい内容かもしれませんが、手を動かして実際にアプリを作ってみるとかなり勉強になると思います。(小並感) 一応ソースコードのリンクも貼っておくので、興味があればぜひ見てみてください!

github.com

明日は53代のsiroさんの記事ですね。お楽しみに!