본문 바로가기

Flutter TabBarView with FutureBuilder, Listview, Pull-Down 갱신

기본 예제만 가지고 상용앱을 만드는 경우는 없다.

 

대부분의 화면은 정적이지 않으며 속된말고 박혀 있는 데이터는 런칭이나 외주를 받을 때 그런 경우는 없다.

여기서 정적이지 않다는 말은 다이나믹하게 바뀐다는 의미는 아니다.

 

여러가지를 조합을 하고 그 과정에서 에러와 삽질 헤딩을 통해서 정석, 편법, 꼼수 등을 활용해서 구현을 해 낸다.

 

현재 기준으로 왕초초초초보가 flutter를 학습중에 고민을 많이 하게 되는 부분이다.

 

상황은 이렇다

scaffold와 더불어 tab을 이용을 해서 상단에는 앱바와 탭뷰가 있고 컨덴츠는 리스트뷰 형태이다.

탭바 메뉴 목록은 10개라고 치자.

 

처음 앱을 실행시 첫번째 탭바뷰에 home에 관한 레이아웃이 나온다.

2번째부터 10번째까지는 일반 리스트뷰 목록이라 레이아웃이 모두 같다. 물론 데이터만 틀리다. 홈페이지의 경우 게시판 이름만 틀리다면 이해가 될까.

 

각각의 탭바뷰는 각기 다른 데이터 json을 네트워크에서 받아서 뿌려준다.

조건.

1.탭바뷰를 swipe로 왔다갔다 할때마다 신규 항목을 불러오는 로딩바가 나오고 신규항목을 불러오진 않는다.

2.탭바뷰를 왔다가 되돌아 와도 이전 화면의 항목은 갱신이 되지 말고 고정이 되야 한다.

3.해당뷰의 목록 데이터가 갱신이 될 경우는 사용자가 pull down을 취했을때 갱신이 되며 새데이터가 나옴과 동시에 화면 갱신이 이루어 져야 한다.

 

학습이나 실제 개발시에도 선코딩 및 구현 후 리팩토링을 한다. 바쁜데 코드를 하나하나 정리하고 나눌 시간까진 없다.

 

여기서 문제가 생긴다.

왕초초초보 Flutter의 사사로운 부딪혔던 에러들을 기록해보자. 그리고 이해하기 편하게 가능하다면 웹과 비교를 해보자.

1.json 데이터 받아오기

-> 1차적으로 받아오는 시점이 문제가 된다. 언제하느냐.

자스라면 다 페이지 되고 도큐먼트 레이디에서 ajax함수를 호출해서 json을 받아서 엘리먼트에 뿌려준다. 그리고 원할 때 별도 함수를 호출을 해서 뿌려준다.

 

_getDataList() {

  ~~~http 제이슨 가져옴.

  create Model;

  return modelArray;

}

2.데이터 불러오는 중이면 로딩바가 끝나면 뿌려주는 리스트뷰가 나오게

FutureBuilder에 많은 예제에는 이러한 부분은 없다. 사용법 정도랄까. 이를 테면

return FutureBuilder(
   future: _getDataList(),
   builder: (BuildContext context, snapshot ) {
      if( snapshot.hasData) {
         return 위젯
      }
      
      return Center(child: CircularProgressIndicator());
   }

)

형태로 되어 있다. 목록 데이터가 있으면 화면에 뿌려주는 리스트 목록을 보여주고 없으면 로딩바가 뜨게 해놨다.

 

이렇게 되면 문제가 발생한다. 

3.tabbarview를 왔다 갔다 할 때마다 갱신 안되게 

탭바뷰를 왔다갔다 할 때마다 계속해서 새로고침 마냥 호출을 하게 된다. 원하는건 최초 한번 로드고 이후는 pull-down일 때만 불러오는 것이다.

 

즉 탭을 왔다갔다 하면 계속 로딩바가 뜨고 매순간 갱신 된다. 원하지 않는 모습이다. 참 부모 위젯은 StatefulWidget이다.

 

최초에 한번만 로딩이 됬으면 좋겠고 이후는 pull-down시에만 되었으면 좋겠다.

 

현재까지는 탭을 왔다 갔다 할 때마다 로딩이 뜨고 갱신이 된다.

 

AutomaticKeepAliveClientMixin 를 State 클래스에 추가해 준다.

https://api.flutter.dev/flutter/widgets/AutomaticKeepAliveClientMixin-mixin.html

class _Hehe extends State<Hehe> with AutomaticKeepAliveClientMixin {
  @override
  bool get wantKeepAlive => true;
  
  Widget build(BuildContext context) {
    return FutureBuilder(
        //future: lists,
        future: getList(),

다시 탭을 왔다갔다 해보자. 이제 로딩바가 처음 이외에 보이지 않는다.

저 선언 없으면 배열을 넣던 배열을 던지는 함수를 넣던 똑같다. hasdata가 없다. 그러므로 로딩이 생긴다.

 

생성자에서 파라메터로 던지는것은 예외로 치고 

첫번째로 최초 한번은 void initState()에서 해준다.

extends State 클래스에 멤버 변수 배열을 하나 선언해 준다.

 

변수 뒤에 ?를 붙이지 않으면 에러가 발생한다.

The argument type 'Future<List<dataModel>>?' can't be assigned to the parameter type 'Future<List<dataModel>>

 

나는 pubspec.yaml의 environment의 버전이 

sdk: ">=2.15.1 <3.0.0" 이기 때문에 null safety 문제가 발생한다. 즉 처리를 해주어야 한다. null이 되게 허용을 해줄려면 ?를 붙인다. 기준이 2.12이상인가보다.

아래처럼 한다. 그럼 초기화때 딱한번 목록을 불러와준다.

class _hehe extends State<hehe>with AutomaticKeepAliveClientMixin {
  @override
  bool get wantKeepAlive => true;
Future<List<dataModel>>? lists;
@override
void initState() {
  lists = _getDataList();
}

class _hehe extends State<hehe>with AutomaticKeepAliveClientMixin {
  @override
  bool get wantKeepAlive => true;
Future<List<dataModel>>? lists;
@override
void initState() {
  lists = _getDataList();
}

4.로딩 유무 판별

return FutureBuilder(

builder: (BuildContext context, snapshot) { or
builder: (BuildContext context, AsyncSnapshot<자료형> snapshot) {

if (snapshot.connectionState != ConnectionState.done)
	return Center(child: CircularProgressIndicator());

//이하 화면에 보일 데이터 뿌려줌
return ListView.buld

5.TabController 연결

extends State<클래스> with SingleTickerProviderStateMixin {

//탭 컨트롤러
  TabController? _tabController;
  
  @override
  void initState() {
    //with SingleTickerProdiverStateMixin을 안해주면 아래 vsync:this 에러남
    _tabController = TabController(length: tabviews.length, vsync: this);
    super.initState();
  }
  
  Widget build의 TabBar와 TabBarView
  TabBar(
  	controller: _tabController,
  ),
  
  body: TabBarView(
  	controller: _tabController

or

DefaultTabController(
        length: 3,

 

5.Pull-Down 구현

ListView.build를 RefreshIndicator으로 감싼다.

listview 윗쪽에서 아래로 당기면 로딩 아이콘이 나타나고 _refreshData가 호출이 된다.

setState()를 호출해주면 화면 갱신이 된다.

이 때 목록을 새로 고침하여 받아올 때 활용 하면 된다.

이 때 pull down의 로딩 아이콘이 뜨고 그 뒤에 또 위에 선언했던 로딩바가 뜨니 플래그 변수를 하나를 주어서 지금 갱신이 된 부분이 어디에서 이루어 졌는지 판단하여 pull-down일 시는 무시하게 한다.

 

결과적으로 보면 앱 실행 후 최초에만 써클이 돌아가고 다신 쓰일 일이 없다. 구조에 따라 다르지만.

return RefreshIndicator (
  onRefresh: _refreshData,
  child: ListView.builder(
)
이런 형태

Future _refreshData() async {
    await Future.delayed(Duration(seconds: 3));
    setState(() {
    });
  }

안 까먹을려고 써놓고 보니 먼 소리를 적어논건지.

 

좀 더 스킬업이 되면 지금보다 좀 더 간편한 방법이 나오거나 잘못 알고 있거나 불편하게 알고 있거나 뭐 하겠지.

공부를 좀 더 하고 provider나 getx를 이용을 해보자.

 

초반 한달만 머리터지게 공부하면 이후 1년이 편하다. 할 때 바짝 하자. 어느 수준까지는.

 

실제 화면 움직이는거 gif로 만들어 시각적으로도 기록해 놓으면 좋을텐데 귀찮구만.