[야나두] flutter - 토스 클론 작업 정리 (Slivers, TabBar, GetX 및 자동완성 기능 관련)

박경수·2024년 3월 11일
0

목적

클론 코딩을 진행하는중에 기억을 정리하기 위함.

작업 내용

  1. 홈, 혜택, 주식, 주식 검색 자동완성, 주식 검색 히스토리 리스트(추가 제거 및 이동).

정리가 필요한 내용

  1. CustomScrollView
    1-1. Slivers(SliverAppBar, SliverToBoxAdapter)
  2. TapBar
  3. 검색 영역...

TapBar

위 영상처럼 좌우 왕복하는 탭바를 구현했던 기록을 작성한다.

코드

class 부모 extends StatefulWidget {


class _StockFragmentState extends State<StockFragment> with SingleTickerProviderStateMixin {
  late final TabController controller = TabController(length: 2, vsync: this);
  int currentIndex = 0;

...

Widget get tabBar => Container(
    color: color,
    child: Column(
      children: [
        TabBar(
          onTap: (newIndex) {
            setState(() {
              currentIndex = newIndex;
            });
          },
          labelStyle: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), /// 라벨(텍스트)의 스타일을 지정
          labelPadding: const EdgeInsets.symmetric(vertical: 20), /// 라벨 패딩
          indicatorSize: TabBarIndicatorSize.tab, /// 좌우 탭클릭시 같이 움직이는 하단 바의 사이즈 지정
          indicatorColor: Colors.white, /// 움직이는 하단 바의 색상 지정
            controller: controller, /// TabBar를 사용하기 위해선, TabController()가 필요함.
            tabs: [
          "내 주식".text.make(), /// 각 탭의 이름 (velocity_x 사용)
          "오늘의 발견".text.make() /// 각 탭의 이름 (velocity_x 사용)
        ]),
        Line()
      ],
    ),
  );
}; // 부모 State
  
}; // 부모

탭에 코드이며, 아래 코드를 정리한다.

late final TabController controller = TabController(length: 2, vsync: this);

TabBar를 이용하기위해선 TabController()가 필수인데, 두가지(length, vsync) 필수 파라미터를 갖는다.
length는 탭의 갯수이며, vsync는 탭바를 동기화 시키기 위함이다.

여기서 vsync에 this를 넣으면 에러가 난다.

공식 홈페이지를 확인해보니, TickerProviderStateMixin and SingleTickerProviderStateMixin classes to obtain a suitable TickerProvider TickerProviderStateMixin나 SingleTickerProviderStateMixin를 넣으면 된다고한다.

둘의 차이는 필요한애니메이션의 갯수로 알고있다.

class _StockFragmentState extends State<StockFragment> with TickerProviderStateMixin {
late final TabController controller = TabController(length: 2, vsync: this);

/// dispose필수
  
  void dispose() {

    controller.dispose();
    super.dispose();
  }

정리

Tabbar는 controller가 필수이며, 컨트롤러에 접근하러면
TickerProviderStateMixin나 SingleTickerProviderStateMixin가 필요하다.

끝.

GetX

Flutter의 상태관리 라이브러리중 하나로,
GetxController에서 데이터를 가져오는 방법이 몇가지 있는데, 나는 아래와 같이 진행했다.

(그 외, 상태관리 라이브러리는 학습 후 포스팅 할 예정이다)

GetxController

  1. view에 데이터를 가져오기 용이 하다.
  2. dispose를 따로 하지 않아도 됨. (자동)
  3. setState처럼 oninit()으로 데이터를 가져온다. (준비)
// 상태 관리할 스크린
class SearchStockScreen extends StatefulWidget {...}
class _SearchStockScreenState extends State<SearchStockScreen> with SearchStockDataProvider {
	Get.put(SearchStockData()); /// getXcontroller를 extends했기에 가능
}

---------------

// GetX controller
class SearchStockData extends GetxController {
	List<SimpleStock> stocks = [];
    
    
    void onInit() {
      loadLocalStockJson();
      super.onInit();
    }

    Future<void> loadLocalStockJson() async {
      final jsonList =
          await LocalJson.getObjectList<SimpleStock>("json/stock_item.json");
      stocks.addAll(jsonList);
    }
}

onInit이 진행 -> loadLocalStockJson메소드 진행 -> stocks에 json데이터 삽입. (더미데이터지만, 후에는 DB에서 값을 받아온다)

Get.put()

그리고 put을 이용해 GetxController를 컨트롤러를 바인딩한다.
이제 언제든지 controller를 통해 변수와 함수에 접근이 가능해진다.

끝.

주식 검색 자동완성

TextField 입력시 자동완성 기능에 대한 정리다.

컨트롤러 추가

/// 검색 위잿을 사용할 스크린
final controller = TextEditingController(); //미리 선언하여 초기화.

Scaffold(
    appBar: StockSearchAppBar(controller: controller,),
...
)
/// 검색 위젯 (StockSearchAppBar)
Expanded(
child: TextFieldWithDelete(
         controller: controller, //상위에서 받아온 컨트롤러 삽입
         textInputAction: TextInputAction.search, //입력폼에 우측 하단의 버튼을 검색 으로 변경
         texthint: "검색영역입니다.", // placeholder
         onEditingComplete: (){ 
         	// 키보드 감춤(context)
		},
	)
)

입력 이벤트 리스너 추가

/// initState에 추가하여 사용
Get.put(SearchStockData());

controller.addListener(() {
    searchData.search(controller.text); // 위에 선언된 입력 컨트롤러의 텍스트를 검색 기능에 추가한다.
});


/// 검색 기능 - SearchStockDataProvider()
  RxList<SimpleStock> autoCompleteList = <SimpleStock>[].obs;

  void search(String keyword) {
    if(keyword.isEmpty) {
      autoCompleteList.clear();
      return;
    }
    autoCompleteList.value =
        stocks.where((element) => element.name.contains(keyword)).toList();
  }

RxList와 obs가 등장하는데 이는 GetX에 실시간으로 데이터 변경를 위해 사용된다.

  • obs = observable로 관잘자 즉, 값의 변화를 감지한다.
  • RxList = Rx형태는 데이터의 변화가 감지되면 실시간으로 반영한다.

search매소드를 통해 받은 keyword의 값이 없다면 자동완성 리스트를 초기화하고 끝낸다.
값이 존재한다면 Stocks(json에서 받은 리스트)에서 where을 통해 텍스트의 값과 stocks리스트의 아이템들을 비교한다.

만약 포함되어있다면 새로운 리스트를 만든다. (필터 기능 완료)

Scaffold(
    appBar: StockSearchAppBar(controller: controller,),
    body : Obx(()=> searchData.autoCompleteList.isEmpty
    	? "대기".text.make()
        : SearchAutoCompleteList(controller),
    )
    
 /// SearchAutoCompleteList()
class SearchAutoCompleteList extends StatelessWidget with SearchStockDataProvider {
  final TextEditingController controller;
  SearchAutoCompleteList(this.controller,{super.key});

  
  Widget build(BuildContext context) {
    return ListView.builder(
      itemBuilder: (context, index) {
        final stock = searchData.autoCompleteList[index]; // SearchStockDataProvider 를 통해 Get.find()로 상태를 조회할 수 있다.
        final stockName = stock.name;
        return Tap(
        onTap: () {
          controller.clear(); // 검색된 리스트를 초기화한다.
          Nav.push(StockDetailScreen(stockName: stockName,)); // 검색된 아이템을 클릭하면 상세 페이지로 이동 간단하게 이름만 넘겼다.
        } ,
        child: stockName.text.make().p(20),
      );
      },
      itemCount: searchData.autoCompleteList.length, // 리스트뷰 빌더는 itemCount를 꼭 사용하자.(에러가 난다)
    );
  }
}

Obx또한, 실시간으로 변화를 감지해 Obx안의 상태를 변경한다.
장점은 변화를 감지하면 Obx영역만 다시 그린다.

(시연 영상 작성중)
끝.

주식 검색 히스토리 리스트

검색 후 클릭해 상세 페이지로 이동한 주식을 텍스트 필드 아래 나열 한다.
이곳도 실시간으로 데이터의 변화가 필요함으로 Obx를 활용한다.

class SearchHistoryStockList extends StatefulWidget {...}

class _SearchHistoryStockListState extends State<SearchHistoryStockList> with SearchStockDataProvider {
	width : double.infinity,
    height: searchData.searchHistoryList.isNotEmpty ? 65 : 20, // 히스토리 리스트가 비었다면 20
    child: Obx(
        () => ListView.builder(
          itemBuilder: (context, index) {
            final stockName = searchData.searchHistoryList[index];
            return Container(
            margin: EdgeInsets.only(right: 8),
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Row(
                  children: [
                    Tap(onTap: (){
                    /// 탭시 디테일 페이지로 이동한다.
                      Nav.push(StockDetailScreen(stockName: stockName));
                    }, child: stockName.text.make(), ),
                    /// x버튼으로 히스토리 제거 기능이다.
                    Tap(onTap: () {
                      searchData.removeHistory(stockName);
                    }, child: Icon(Icons.close))
                  ],
                )
                    .box
                    .withRounded(value: 6)
                    .color(context.appColors.roundedLayoutBackground)
                    .p8
                    .make(),
              ],
            ),
          );
          },
          itemCount: searchData.searchHistoryList.length,
          scrollDirection: Axis.horizontal,
        ),
      ),
    }
    
 /// 추가로 주식 검색후 자동완성을 통해 만들어진 아이템을 클릭하면 historyList에 추가하는 코드도 작성한다.

SearchAutoCompleteList() {
 final TextEditingController controller;
  SearchAutoCompleteList(this.controller,{super.key});

  
  Widget build(BuildContext context) {
    return ListView.builder(
      itemBuilder: (context, index) {
        final stock = searchData.autoCompleteList[index];
        final stockName = stock.name;
        return Tap(
        onTap: () {
          controller.clear();
          searchData.addHistory(stock); //추가한 부분
          Nav.push(StockDetailScreen(stockName: stockName,));
        } ,
        child: stockName.text.make().p(20),
      );
      },
      itemCount: searchData.autoCompleteList.length,
    );
  }
 }

이로써 아래와 같은 로직이 만들어졌다.
검색 버튼 클릭(검색 스크린 이동) -> 텍스트 필드에서 주식 검색(자동완성 리스트 생성) -> 아이템클릭(상세 페이지 이동, 히스토리 리스트 저장, 및 뒤로가기) -> 히스토리 리스트를 기반으로 검색 리스트 생성된

완성 영상

작성중

profile
<>{...}</>

0개의 댓글

관련 채용 정보