개발/Android

[Flutter] 간단한 ToDo앱에서 MethodChannel 응용

y_lime 2025. 3. 10. 09:15

Flutter 앱과 안드로이드 네이티브 코드(Kotlin) 간의 통신을 위한 플러그인(Platform Channel) 구현 코드


📌 주요 역할

  • Flutter에서 보낸 메소드 호출(Method Call) 을 안드로이드 네이티브에서 처리
  • 할 일 목록(To-Do List)을 저장하고 불러오는 기능 제공
  • SharedPreferences를 이용해 데이터를 저장 및 관리

📂 코드 상세 설명

1. 기본 클래스 및 변수 선언

class MainActivity : FlutterActivity() {
    private val CHANNEL_NAME = "com.example.to_do_list/task_channel"
    private val TAG = "MainActivity"
  • MainActivity는 FlutterActivity를 상속받아 Flutter와 연결
  • CHANNEL_NAME: Flutter와 통신할 때 사용하는 채널 이름 (Flutter에서 이 이름으로 메소드 호출)
  • TAG: Log.d(TAG, "...") 같은 방식으로 로그를 출력할 때 사용

2. Flutter와 통신하는 코드 (configureFlutterEngine 메서드)

override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
    super.configureFlutterEngine(flutterEngine)
    MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL_NAME)
        .setMethodCallHandler { call, result -> 
  • configureFlutterEngine: Flutter와 네이티브(Android) 코드를 연결하는 역할
  • **MethodChannel**을 생성해서 CHANNEL_NAME으로 Flutter와 연결
  • setMethodCallHandler: Flutter에서 특정 메소드를 호출하면, 여기서 받아서 처리

3. Flutter에서 보낸 메소드 처리

(1) updateTasks → 특정 날짜의 할 일 목록 저장

"updateTasks" -> {
    val date = call.argument<String>("date")
    val tasks = call.argument<List<*>>("tasks")
    if (date != null && tasks != null) {
        val jsonArray = JSONArray(tasks)
        val sharedPref: SharedPreferences =
            getSharedPreferences("Tasks", Context.MODE_PRIVATE)
        sharedPref.edit().putString(date, jsonArray.toString()).apply()
        Log.d(TAG, "tasks update: $date, tasks: $tasks")
    } else {
        result.error("INVALID_ARGUMENT", " null", null)
    }
}

📌 동작 방식

  1. date (날짜)와 tasks (할 일 목록)를 Flutter에서 전달받음.
  2. tasks를 JSON 배열(JSONArray)로 변환하여 저장.
  3. SharedPreferences를 사용해 "Tasks"라는 저장소에 데이터를 저장.
  4. 저장이 끝나면 Log.d()로 로그 출력.

💡 SharedPreferences란?

  • 간단한 데이터를 앱 내부 저장소(로컬 저장소) 에 저장하는 방식.
  • Map 구조인 Key - Value 형태로 저장.
  • 앱이 종료되더라도 데이터가 유지됨.
  • 앱이 삭제되기 전까지 내부에 보관됨
  • Mode 종류
 MODE_PRIVATE  생성한 애플리케이션에서만 사용 가능
, 외부 APP에서는 접근 불가 (Default)
 MODE_WORLD_READABLE  외부 App에서 사용 가능, 읽기만 가능 
 MODE_WORLD_WRITEABLE 외부 App에서 사용 가능, 읽기/쓰기 가능

(2) getTasks → 특정 날짜의 할 일 목록 불러오기

"getTasks" -> {
    val date = call.argument<String>("date")
    if (date != null) {
        val sharedPref: SharedPreferences =
            getSharedPreferences("Tasks", Context.MODE_PRIVATE)
        val tasksString = sharedPref.getString(date, null)
        if (tasksString != null) {
            val jsonArray = JSONArray(tasksString)
            val tasksList = mutableListOf<Map<String, Any>>()
            for (i in 0 until jsonArray.length()) {
                val jsonObj = jsonArray.getJSONObject(i)
                val task = jsonObj.getString("task")
                val isDone = jsonObj.getBoolean("isDone")
                tasksList.add(mapOf("task" to task, "isDone" to isDone))
            }
            Log.d(TAG, "date: $date, tasks: $tasksList")
            result.success(tasksList)
        } else {
            result.success(emptyList<Map<String, Any>>())
        }
    } else {
        result.error("INVALID_ARGUMENT", "null", null)
    }
}

📌 동작 방식

  1. date를 Flutter에서 전달받음.
  2. SharedPreferences에서 date에 해당하는 저장된 JSON 데이터를 가져옴.
  3. JSON 데이터를 리스트(Map 형태) 로 변환하여 Flutter로 반환.
  4. Log.d(TAG, "...")로 데이터를 출력.

(3) getAllTasks → 모든 날짜의 할 일 목록 불러오기

"getAllTasks" -> {
    val sharedPref: SharedPreferences =
        getSharedPreferences("Tasks", Context.MODE_PRIVATE)
    val allEntries = sharedPref.all
    val allTasks = mutableMapOf<String, Any>()
    for ((key, value) in allEntries) {
        if (key.length == 8 && value is String) {
            try {
                val jsonArray = JSONArray(value)
                val tasksList = mutableListOf<Map<String, Any>>()
                for (i in 0 until jsonArray.length()) {
                    val jsonObj = jsonArray.getJSONObject(i)
                    val task = jsonObj.getString("task")
                    val isDone = jsonObj.getBoolean("isDone")
                    tasksList.add(mapOf("task" to task, "isDone" to isDone))
                }
                allTasks[key] = tasksList
            } catch (e: Exception) {
                Log.e(TAG, "Key Error: $key", e)
            }
        }
    }
    Log.d(TAG, "all tasks: $allTasks")
    result.success(allTasks)
}

📌 동작 방식

  1. SharedPreferences에 저장된 모든 데이터를 가져옴.
  2. allEntries에서 날짜 형식(YYYYMMDD)인 키만 필터링.
  3. 각 날짜별로 JSON 데이터를 변환하여 Flutter로 반환.
  4. Log.d(TAG, "...")로 전체 할 일 목록을 출력.

📝 정리

메소드 설명

updateTasks 특정 날짜의 할 일을 저장
getTasks 특정 날짜의 할 일 목록 불러오기
getAllTasks 모든 날짜의 할 일 목록 불러오기

🔥 주요 기술

  • MethodChannel: Flutter와 안드로이드 간의 통신
  • SharedPreferences: 간단한 데이터를 앱 내부 저장소에 저장
  • JSON 처리: JSONArray, JSONObject를 활용하여 데이터 변환

🚀 Flutter에서 사용 예시

💡 Flutter에서 위 메소드를 호출하는 방법 (task_notifier.dart)

const platform = MethodChannel('com.example.to_do_list/task_channel');

// 1. 할 일 목록 저장
 Future<void> _saveTasksForDate(
    DateTime date,
    List<Map<String, dynamic>> tasks,
  ) async {
    final dateKey = _formatDate(date);

    await platform.invokeMethod('updateTasks', {
      'date': dateKey,
      'tasks': tasks,
    });
  }

// 2. 특정 날짜의 할 일 불러오기
  Future<void> loadTasks() async {
    final dateKey = _formatDate(state.selectedDate);
    final result = await platform.invokeMethod('getTasks', {'date': dateKey});
    if (result is List) {
      final tasksForDate =
          result.map((e) => Map<String, dynamic>.from(e)).toList();
      state = TaskModel(
        selectedDate: state.selectedDate,
        tasks: {...state.tasks, state.selectedDate: tasksForDate},
      );
    }
  }
  
// 3. 모든 할 일 불러오기
 Future<void> loadAllTasks() async {
    final result = await platform.invokeMethod('getAllTasks');
    if (result is Map) {
      final Map<DateTime, List<Map<String, dynamic>>> tasksMap = {};
      result.forEach((key, value) {
      //이때 result에서 key가 String으로 반환되기 때문에 년,월,일을 따로 저장하여 다시
      //DateTime으로 변경해줘야한다.
        final year = int.parse(key.substring(0, 4));
        final month = int.parse(key.substring(4, 6));
        final day = int.parse(key.substring(6, 8));
        final date = DateTime(year, month, day);
        if (value is List) {
          tasksMap[date] =
              value.map((e) => Map<String, dynamic>.from(e)).toList();
        }
      });
      state = TaskModel(selectedDate: state.selectedDate, tasks: tasksMap);
    }
  }