0. 이글을 쓰기시작한 이유.
예~전에 한참 스위프트 공부에 재미들여서 했을적에 서버드리븐 유아이라는 개념을 듣고 언젠간 공부해봐야지 하고 메모해놨던 일이 있다.
지금이 24년이고, 내가 앱에 입문했을때가 18년이니까 대충 20년쯤이지 아니였을까하는데 뭐 하나 정리를 제대로 안해두는 내 성격상에 여태 까맣게 잊고있다가 최근에 flutter를 공부하다 다시 생각이 났다.
개념자체는 엄청 신기하거나 그러진 않고 앱의 화면에 대한 정보를 서버에서 저장해놓고 앱은 해당 정보를 받아와서 그려는식의 웹뷰를 네이티브 형식으로 구현한 느낌이다.
아 물론 이 설명은 내가 슬쩍 검색해보고 나오는 게시글들의 감상이다
이런식이면 이게 웹앱인지 네이티브앱인지 아리송해지긴하는데 솔직히 요새 서버에서 데이터 받아와서 테이블이나 리스트에 뿌려주는거 보면 어처피 이게 그거 아닌가 싶어서 오히려 왜 방법이 스타트업 사이에서 굴러지고 있는 사람들이 주로 쓰고있지않은건가 의문이든다
요즘은 애플의 앱심사가 정말 빨라졌다지만 그래도 aws에서 파이프라인만들어놔서 빌드하면 자동으로 게시가 되는거 웹페이지에 비교하기에는 너무 천지차이이고 이제막 시작하는 앱이면 이곳저곳 버그가 산재해있거나 바뀌는 아이템으로 앱업데이트를 자주해야하는 상황에선 정말 답답하게된다
이런 상황으로 이전에는 앱시작시 팝업 정보가 있는지 서버에 저장해놨다가 팝업정보가 있으면 앱시작시 띄여주고 해당 팝업 선택시 웹페이지로 연결해주는 방식으로 했었는데 (내 이전직장들이 죄다 망해버려서 오랫동안 쓰이진 않았다) 이런것보단 차라리 server driven ui가 맞지않을까 싶다
UI는 네이티브로 고정해두고 내부 아이템들의 서버변화를 줘서하는거의 크게 다르지 않아서 굳이 기존앱의 변경하는 메리트는 없어보인다
그런데도 이 서버드리븐방식의 글을쓰게된이유는...
블로그에 글을 너무 안올리고있고 거의 반년째 일이 없어 날백수생활을 하고있기때문이다.
뭐라도 해야지....
그렇게 난 flutter로 server driven ui의 구현에 대한 테스트앱을 만들어보기로 한다.
1. 아이디어 정리
원래는 하루동안 잠깐만 하려고 했던게 조금 쓰고 이제서야 다시 잡았다.
현 시점이 0번항을 적고 나서 시일이 좀 지난 시점이라 당시 어떤 생각을 했는지 좀 잊어버렸다
살짝 짜둔 코드를 보며 다시 아이디어를 정리하고 가야겠다.
내 앱은 우선
전체 구성은 변경하지 않도록 했다 이것도 잘 생각해보면 앱 구성자체를 코드형태로 서버에 저장했다가
스플래시화면에서 받아서 렌더링하는 방법으로 해도 될듯하긴한데 이렇게 하면 차라리 웹앱으로 하는게 맞지않나 싶어서 이것은 포기했다
그렇다면 어느정도 틀만 만들어두고 해당 틀내용만 받아오는식으로 할 생각인데
대강 각 탭 페이지는 라우트로 만들어두고
각 페이지는 카드로 구성되어 있고 각 카드는 몇 종류로 나눠있으며 해당 카드를 미리 만들어두어( 스위프트 uikit의 테이블셀 처럼 )
페이지 첫 로드시 각 카드의 순서, 종류, 종류에 따른 내부 컨텐츠 값을 받아와서 화면에 그려주는 방식으로 하려고 한다
생각이 깊어질수록 플러터 기본 제공 위젯을 이용하면되는게 아닌가 싶어지기도하는데 이래서 따로 안만들어두고 차라리 웹앱으로 가던가 하는건가....
특정 디자인이 이미 구현되어 있고 꽤나 다른 앱과 다른 유니크한 디자인이라면 일종의 UIkit을 만들어서 제공하는게 있을지도 모르겠다
대기업 앱엔 있을지도?
뭐 아무튼 이번 프로젝트는 방법이 구현이 되는가가 중요하기에 범위자체는 크게 하지 않을거다
많은 앱에서 사용하는 배너용 카드, 단순 글 알림카드?, 사진들어간 좀 큰카드 이렇게 세가지 정도만 화면에서 보여지고
이걸 보여주기위한 넓~직한 카드 읽기용 글카드(사진 포함) -> 단순 글카드와 사진들어간 큰카드 공용으로 쓰고
보통 구현한다는 crud에서 r만 해서 간단히 보여주기 자세히보여주기 만하자
그렇지만 너무 내용이 없어보이면 추가로 이것저것 추가해야겠다
그래서 생각나는데로 적은 글들을 정리하자면
1. 메인 페이지 겉부분은 코드로 작성
2. 페이지에 들어가는 내용은 '카드'단위로 나누어서 각 카드별 디자인코드 작성
3. 페이지 로드시 페이지에 들어갈 카드내용을 불러와서 랜더링
4. 카드 내용은 종류, 본문 + 연결될 링크(앱 페이지~ 또는 웹링크~)
간단하게 할거라 디자인도 기본으로 제공해주는 위젯을 살짝 래핑하는 식으로 할거다.
일단 다음에 해야지....
// ---------
25.01.16일 재시작
프로젝트 시작만 해두고 냅둔애들이 너무 쌓이기시작해서 일단 빠르게 끝낼수 있는 이것먼저 끝내기로함.
위 구성은 잠깐 한두시간한다고 끝낼수 있을거같지않아서 내용을 줄이려한다.
페이지는 2개, 들어가는 스플래시 페이지와, 메인페이지
스플래시는 원래 전체 앱구성을 받고 각 페이지를 웹페이지마냥 코드 읽어서 그려줄려고했는데
스플래시는 하는일 없이 그냥 넘기기로 함
카드구성도 좀 다양하게 만들고 나중에도 써먹을수 있게 템플릿형태로 만들려고 한거같은데 수정
그냥 적당히 4가지 형태로 하고 코드 구현도 chat gpt 무료버전을 이용해 채우도록 하려함
지금이 새벽 3시반 정도되는데 아침되기전에 마무리하고 포스트 업로드하는거로 목표
2. 구현
일단 아이디어 답게
페이지가 열리면 api로 화면에 대한 데이터값을 요청 -> 값을 받으면 코드를 그림
이러는 형식으로 했다
Future<List<CardDatum>> fetchPageData() 를 구현하고
빌드에서 해당 함수를 호출
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async => false,
child: Scaffold(
body: SafeArea(
child: FutureBuilder<List<CardDatum>>(
future: fetchPageData(),
builder: (context, snapshot) {
if (snapshot.hasData) {
if (snapshot.data!.isEmpty) {
return const CircularProgressIndicator();
}
var cards = buildCards(snapshot.data!);
return ListView(
children: [...cards],
);
} else if (snapshot.hasError) {
return Text('${snapshot.error}');
}
return const CircularProgressIndicator();
},
)),
),
);
}
값에 대한 오류가 났을때 재로드하는 식으로 해줄수도 있지만 졸리고 빨리 끝내고 싶어서 로딩바가 계속 굴러가게 했다.
buildCards() 함수는
List<Widget> buildCards(List<CardDatum> cardData) {
List<Widget> cardList = [];
// 카드 밑에 높이8의 빈공간 추가
for (var datum in cardData) {
CardType? type = datum.cardType();
if (type == null) {
ErrorOBj eobj = ErrorOBj(
type: "build error", message: "카드 데이터의 타입이 올바르지 않다(${datum.type})");
print(eobj.toString());
continue;
}
Widget cardWidget = BuildCardUtil().build(type, datum.data);
cardList.add(cardWidget);
Widget blank = const SizedBox(height: 8);
cardList.add(blank);
}
return cardList;
}
따로 타입별 위젯 만드는 클래스를 분리하고 오류로그 찍는 정도 + 각 카드별 공백 잡아줬다
카드 빌드하는 함수는
class BuildCardUtil {
Widget build(CardType type, dynamic data) {
Map<String, dynamic> map = data as Map<String, dynamic>;
switch (type) {
case CardType.banner:
String imageUrl = map["imageUrl"] as String;
String link = map["link"] as String;
return ImageBannerCard(imageUrl: imageUrl, link: link);
case CardType.profile:
String name = map["name"] as String;
String bio = map["bio"] as String;
String imageUrl = map["imageUrl"] as String;
return ProfileCard(name: name, bio: bio, imageUrl: imageUrl);
case CardType.squareImage:
String imageUrl = map["imageUrl"] as String;
return SquareImageCard(imageUrl: imageUrl);
case CardType.titleAndSub:
String title = map["title"] as String;
String subtitle = map["subtitle"] as String;
return TitleSubtitleCard(title: title, subtitle: subtitle);
default:
return Container();
}
}
}
이런식으로 각 카드클래스를 따로 만들어 연결해주는 방법으로 한단계를 분리하여 확장하기 편하게 했으나...
딱히 앞으로 확장할일이 없기에 괜한일을 했나싶다 한두시간내에 끝내려고했는데 이미 6시가 가까워지고있어서 그냥 손가락 가는데로 타이핑했다
추가로 각 카드 클래스들은 gpt를 이용해 뽑아서 간단하게 수정만해줌...
무료버전이라 그런가 애가 실수가 많네...
api는 어떻게 해줄까 한참을 고민하고 로컬로 서버 만들어서 할까 아니면 파베에서 공짜로 주는 함수기능을 이용할까
검색하다가 포스트맨을 이용해 mockApi를 만들어 제공해준다길래 이미 깔려있기도해서 이거 이용해봤다
매우 만족!!!
이런 좋은 기능을 이제야 알았다니..
아래 사진처럼 요청주소, 반환값 이런거 저장해두고
아래 mock servers에서 생성해주면!!!!
요청 로그까지 찍어준다!
그렇게 아래와같은 예제가 완성되었다
실 컨텐츠값은 서버에서 제공하고 앱은 카드형식 만드는거만 지정해서 카드추가와 삭제는 서버에서 지정이 가능한
나름 서버드리븐 앱
원래 구상은 좀더 앱같이 만드려고했으나 어쩌다보니 이렇게 간단한 예제가 되어버리며 프로젝트를 마무리한다.
코드는 내 깃허브에 올릴것이다
+ 추가 깃링크
https://github.com/wiwi-git/server_driven_test
'flutter' 카테고리의 다른 글
Flutter 카카오 로그인 misconfigured, error_description: invalid android_key_hash or ios_bundle_id or web_site_url 에러 (0) | 2024.07.28 |
---|---|
CocoaPods could not find compatible versions for pod "sqflite" - flutter ios 에러 (0) | 2024.02.04 |
Dart: 상속관계에서의 오버라이드한 파라미터 설정 (0) | 2023.12.14 |
Dart, Alpha2 국가코드 toMap (2) | 2023.11.27 |