TIL

TIL - 지역 검색 앱 (1) <RiverPod의 로직 순서>

hamiric 2024. 12. 2. 12:05

## 해당 TIL은 주어진 과제를 수행하면서 얻은 학습 내용과, 시행착오 등등을 종합해서 작성한것임

 

1. RiverPod 메서드 동기적으로 작동시키기

해당 코드는 IconButton을 눌렀을때 로딩 트리거를 발생시키고, 현재 위치의 검색하는 로직이다. 

IconButton(
  onPressed: () {
    ref.read(homeViewModelProvier.notifier).startLoading();
    ref.read(homeViewModelProvier.notifier).searchCurrentLocation();
  },
  icon: Icon(Icons.gps_fixed),
),

 

여기서 추가로 구현하고자 하는 것은, 해당 버튼을 눌렀을때, 현재 위치가 검색된 후
TextField의 text가 바뀌도록 하는것이다.

 

** ( 현재 TextField의 controller.text는 homeState와 연결되지 않은 상태 - 일부러인데, 매번 값을 입력할 때마다 onChange를 통한 상태 업데이트를 해주는것이 손해라고 생각했기 때문에...)

 

여기서 중요한것은, searchCurrentLocation 메서드는 비동기 함수로써, 현재 위치를 가져오는데 시간이 걸린다는 것이었다.

 

 

< 첫번째 시도 >

IconButton(
  onPressed: () {
    ref.read(homeViewModelProvier.notifier).startLoading();
    ref.read(homeViewModelProvier.notifier).searchCurrentLocation();
    _searchController.text = homeState.controllerText;
  },
  icon: Icon(Icons.gps_fixed),
),

 

해당 코드는 정상적으로 작동하지 않았는데, 그 이유는 위의 코드들이 동기적으로(순차적으로) 작동하지 않기 때문이었다.

아니, 사실 위에서 코드는 동기적으로 작동하나, 그건 ref.read 부분만 그런것이었다.

실제로 ref.read는 각각 "~ 메서드를 실행시켜라!" 라는 명령만 내리는 코드이기 때문에 발생하는 오해였다.

 

즉, 뒤의 startLoading과 searchCurrentLocation은 비동기적 작동을 하고 있으므로,

_searchController.text = homeState.controllerText 부분이 실행될때는 아직 해당 값이 업데이트 되지 않은 상태였던 것이다.

 

 

< 두번째 시도 >

IconButton(
  onPressed: () async {
    await ref.read(homeViewModelProvier.notifier).startLoading();
    await ref.read(homeViewModelProvier.notifier).searchCurrentLocation();
    _searchController.text = homeState.controllerText;
  },
  icon: Icon(Icons.gps_fixed),
),

 

첫번째 시도를 발판삼아 억지로 동기적으로 해보려는 시도였으나,

해당 코드는 오류 범벅이다.

 

왜냐하면, 지금 사용되는 ref.read 는 동기적인 함수이며, 동기적으로 상태를 반환하기 때문이었다.

즉, await를 사용할 수 없는 코드라는 뜻이다.

 

그럼 어떻게 searchCurrentLocation이 끝나고 난뒤 text가 업데이트 되도록 해야 하는 걸까?

 

 

< 세번째 시도 >

IconButton(
  onPressed: () async {
    await ref.read(homeViewModelProvier.notifier).startLoading();
    await ref.read(homeViewModelProvier.notifier)
      .searchCurrentLocation()
      .then((_){
        _searchController.text = homeState.controllerText;
    })
  },
  icon: Icon(Icons.gps_fixed),
),

 

.then을 사용하는 방법이다.

searchCurrentLocation 메서드에 .then을 넣어줌으로써, 해당 비동기 작업이 끝날때 then 안의 작업이 실행되도록 하는 방식이다.

 

해당 방법은 await를 사용할 수 없는 코드에서 반드시 순차적으로 작업이 이어져야 할때 사용되는 방식이다.

 

그러나 결과적으로 이 코드도 정상작동하지 않았다.

그 이유는 다름아닌, homeState.controllerText가 업데이트 되지 않은 값을 가져오기 때문이었다.

 

이는 .then이 작동되는 타이밍과 ref.watch의 작동방식에 대한 문제였다.

searchCurrentLocation 메서드를 살펴보면,

// searchCurrentLocation() 마지막 부분
state = HomeState(response, false, query);

 

이와 같이 UI 까지 업데이트하고 있는데, .then의 경우 그 이후에 이루어지는 로직이다.

 

간단히 설명해 보면,

원래 text가 A 라는 값을 가지고 있는 와중, searchCurrentLocation으로 B라는 값을 받아온다고 하자.

searchCurrentLocation메서드가 작동되면, text의 상태를 B로 바꾸어 주게 된다. 다만, 현재 UI의 text는 A 그대로인 상태.

( 현재 UI를 그리는 부분에 text를 watch와 연결해 주지 않은 상태인데, TextField의 특징으로 인해 의도한 바라서..)

 

이 상태에서 .then을 통해 _searchController.text = homeState.controllerText을 수행하게 되는데,
UI의 text는 B로 바뀌게 되었으나, 상태가 변경된 것이 아니므로, ref.watch는 업데이트를 수행하지 않는 현상이 발생한다.

 

따라서 사용자가 보이는 UI text는 A 로 보이게 되는것이다!!!

 

 

< 네번째 시도 >

IconButton(
  onPressed: () async {
    await ref.read(homeViewModelProvier.notifier).startLoading();
    await ref.read(homeViewModelProvier.notifier)
      .searchCurrentLocation()
      .then((_){
        final text = ref.read(homeViewModelProvier).controllerText;
        _searchController.text = text;
    })
  },
  icon: Icon(Icons.gps_fixed),
),


따라서 .then 이후 한번더 read를 받아서 화면을 재 업데이트 함으로써, 해당 문제를 해결하게 되었다!