앱을 개발할 때 중요한 요소중에 하나는 직관적인 UI를 갖추는 것이며, Dismissible은 리스트 관리 시 유용하게 사용할 수 있는 widget중 하나입니다. 오늘 글은 Dismissible을 사용하면서 발생한 ‘Dismissible widget is still part of the tree’ 오류에 대해서 정리해보고 이를 해결하는 과정을 공유해보겠습니다. 시행착오 과정을 정리하였으며 비슷한 내용으로 고생하시는분들께 조금이나마 도움이 되기를 바랍니다.
오류현상
ListView.Builder를 이용해서 데이터베이스로부터 읽은 데이터를 화면에 리스트로 보여주고 Card 위젯으로 구성된 리스트를 Dismissible을 이용하여 삭제하는 기능을 적용하고자 하였습니다. 그래서 onDismissed에 데이터베이스에서 데이터를 삭제하는 함수와 리스트에서 해당 아이템을 제거하는 부분을 함께 작성하였으나 다음과 같이 A dismissed Dismissible widget is still part of the tree라는 오류가 발생하였습니다.
접근1. UniqueKey()를 이용
구글링을 통해서 확인하였을 때 key를 유니크하게 설정하지 않아서 발생하는 문제인 경우가 가장 많은 듯 하였습니다. 그래서 기존에 데이터베이스의 id값(Unique)으로 설정하였던 key를 UniqueKey()로 바꿔서 설정하였으나 갑자기 Dismissible이 작동하지 않는 상황이 발생하였습니다. 아예 swipe가 작동하지 않고 고정되어 있었습니다. 좌우로 밀어보면 살짝 밀리는 모습이 보여서 dismissible이 아예 적용이 안되는 것 같이 보이지는 않았으나 여튼 작동하지 않았습니다.
ValueKey나 Key를 이용하였을 때에는 swipe는 작동하였는데 UniqueKey를 이용ㅎ알 경우 swipe가 적용되지 않는 현상이 발생하였습니다.
접근2. onDismissed에 removeAt 이용
다음으로 시도해 본 방법은 removeAt을 이용해서 데이터베이스에서 삭제한 후 리스트에서도 함께 삭제하였습니다. 예제를 통해서 보는 많은 Dismissible은 리스트를 기반으로 보여집니다. 그래서 removeAt을 이용하여 List에서 아이템을 지워주기만 하면 되는 심플한 예제들이 많습니다. 제가 적용하려고 한 것은 데이터베이스로부터 읽어온 리스트를 삭제하는 것이었기 때문에 onDismissed에 선택한 데이터의 id에 해당하는 데이터를 삭제하는 코드를 입력하였습니다.
추측하기로는 swipe를 하여 데이터를 삭제할 때 데이터베이스에 쿼리가 적용되어 Stream을 통해서 데이터 목록이 다시 ListView에 반영되기까지 시간이 걸려 어플리케이션이 목록에 남아있는 아이템을 인지한 것이 아닐까 싶었습니다. 그래서 데이터베이스에서 삭제하는 동시에 데이터베이스로부터 반환받았던 아이템 리스트에서도 함께 삭제하는 방법을 시도해 보았습니다. 결과는 반만 성공입니다.
정상적으로 삭제되는 데이터도 있는 반면에 그렇지 못하고 동일한 오류를 반환하는 경우가 생겼습니다.
접근3. 비동기화 접근
데이터베이스에서 데이터를 삭제한 뒤 새롭게 목록이 업데이트되면서 삭제한 데이터가 목록에서 사라지게되면 Dismissible은 ‘widget is still part of the tree’라는 오류를 발생시키지 않을 것입니다. 이야기인 즉, onDismissed에서 데이터를 삭제하고 데이터베이스가 다시 반영되기까지의 절차가 완료되어야 함을 의미합니다.
ListView의 경우 스트림으로 데이터를 읽어오기 때문에 삭제 이벤트가 발생하면 데이터베이스가 업데이트되면서 새로운 목록이 적용될 것입니다. 이 모든 일련의 절차를 순차적으로 진행하기 위해서 비동기식 프로그래밍인 async/await를 이용하였습니다.
문제는 데이터베이스에서 데이터가 삭제된 되 스트림을 통해서 어느 시점에 리스트가 업데이트되는지 확인할 수 있는 방법이 없다는 것입니다. 임시방편이기는 하지만 삭제 후 Future.delay 위젯을 이용해서 강제로 특정 시간만큼 (예를들어 3초) 기다리도록 하였습니다. 물론 removeAt을 이용하여 목록에서도 강제로 지워주었습니다.