내배캠 43일차 _ Flutter 심화


 1. Flutter 심화


무한스크롤 : NotificationListener 위젯 사용

onNotification속성에 정의한 함수 실행


스크롤 시작 ScrollStartNotification 객체

스크롤 이동할 때 ScrollUpdateNotification 객체 

스크롤 끝났을 때 ScrollEndNotification 객체


스크롤 이동할 때 ScrollUpdateNotification 객체 

>> ScrollMetrics 타입, metrics속성


metrics.pixels: 현재 스크롤 위치

metrics.maxScrollExtent : 스크롤 가능한 최대범위

metrics.minScrollExtent : 스크롤 가능한 최소범위

metrics.extentAfter 현재 위치 이후 남은 스크롤 거리

metrics.extentBefore : 현재 위치 이전 남은 스크롤 거리




NotificationListener(
onNotification: (notification) {
if (notification is ScrollUpdateNotification) {
if (notification.metrics.pixels >=
notification.metrics.maxScrollExtent) {
// 여기서 업데이트
}
}
return true;
},
child: ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
},
),
)




실습코드


import 'package:flutter/material.dart';

void main() {
runApp(const MyApp());
}

class MyApp extends StatelessWidget {
const MyApp({super.key});

@override
Widget build(BuildContext context) {
return MaterialApp(
home: HomePage(),
);
}
}

class HomePage extends StatefulWidget {
const HomePage({super.key});

@override
State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
List<int> items = List.generate(20, (index) => index).toList();


*************************************************************** FetchMore
// 데이터 불러오는 도중 사용자가 스크롤 이동해서 중복 요청될 경우 방지용
bool isFetching = false;
void fetchMore() async {
if (isFetching) {
return;
}
print("fetchMore");
isFetching = true;
await Future.delayed(Duration(seconds: 3));
final newList =
List.generate(20, (index) => index + items.last + 1).toList();
items.addAll(newList);
setState(() {});
isFetching = false;
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: NotificationListener(
onNotification: (notification) {
if (notification is ScrollUpdateNotification) {
if (notification.metrics.pixels >=
notification.metrics.maxScrollExtent) {
fetchMore();
}
}
return true;
},

********************************************************* NotificationListener
child: ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return Container(
alignment: Alignment.center,
color: Colors.amber,
padding: EdgeInsets.all(20),
margin: EdgeInsets.all(20),
child: Text('${items[index]}'),
);
},
),
),
);
}
}





Go Router


import 'package:flutter_go_router_example/pages/error_page.dart';
import 'package:flutter_go_router_example/pages/home_page.dart';
import 'package:flutter_go_router_example/pages/post_detail_page.dart';
import 'package:flutter_go_router_example/pages/post_list_page.dart';
import 'package:flutter_go_router_example/pages/search_page.dart';
import 'package:go_router/go_router.dart';

// GoRouter configuration
// GoRouter 객체로 경로 설정
final router = GoRouter(
routes: [
// 특정 경로로 이동했을 때 보여줄 페이지 설정하는 객체
GoRoute(
path: '/',
builder: (context, state) => const HomePage(),
// 중첩경로 설정할 때 GoRoute 객체의 routes 속성에 정의
routes: [
// /post 로 접속 시 네비게이션 스택에 HomePage -> PostListPage 쌓임
GoRoute(
path: 'post',
builder: (context, state) => const PostListPage(),
routes: [
// /post/:id
GoRoute(
path: ':id',
builder: (context, state) {
// GoRouterState
// path parameter는 string 타입으로 넘어옴
final id = int.tryParse(state.pathParameters['id'] ?? '');
if (id == null) {
return const ErrorPage();
}
return PostDetailPage(id: id);
},
),
],
),
// /search
GoRoute(
path: 'search',
builder: (context, state) {
// GoRouterState
// url 뒤의 쿼리 파라미터에 접근
final text = state.uri.queryParameters['text'] ?? '';
if (text.trim().isEmpty) {
return const ErrorPage();
}
return SearchPage(text: text);
},
),
],
),
],
// 최초 위치
initialLocation: '/',
// redirect: (context, state) {
// if(특정한 조건){
// return 다른경로
// }
// // redirect
// return null;
// },
// 잘못된 경로 입력했을 때
errorBuilder: (context, state) {
return const ErrorPage();
},
);






Material App 설정


class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp.router(
routerConfig: router,
);
}
}





페이지 이동


// 123 은 pathParameter
context.go('/post/123');
// Uri 객체를 이용해서 queryParamter도 전달 가능
context.go(Uri(path: '/search', queryParameters: {'text': 'abc'}).toString());
// 직접 쿼리파라미터 전달
context.go('/search?text?=abc');
// extra 파라미터를 이용해 객체 전달도 가능
User user = User();
context.go('/123', extra: user);
// context.push 도 있는데 사용 시 URL 경로를 기반으로 네비게이션 스택을
// 관리해주는 것이 아닌 현재 페이지 스택에 새로 페이지를 쌓는 형태라서
// 사용 권장되지 않음!







2. 인사이트 클럽




마이클 자이벨 - MVP 계획하는 방법


https://www.youtube.com/watch?v=1hHMwLxN6EM



1. MVP란 무엇인가?

MVP는 터무니없이 단순한 것입니다 [00:27]. 이는 가장 먼저 목표로 하는 소수의 사용자들에게 제공하여, 제품이 그들에게 조금이라도 가치를 제공할 수 있는지 확인하기 위한 첫 번째 단계입니다 [00:33].
  • MVP 구축 전: 제품을 만들기 전에 사용자들과 대화하는 것이 중요하며, 본인 스스로가 사용자인 경우 더욱 쉽습니다 [01:01].

2. 초기 스타트업의 목표 (4가지 핵심 단계)
MVP를 출시하기 전 스타트업의 목표는 극도로 단순해야 합니다.
  1. 빨리 출시하세요 (Launch Quickly): 가장 중요한 조언은 **"엉망인 제품이라도 빨리 출시하라"**는 것입니다 [01:57]. 많은 창업자가 첫 사용자 접촉 없이 여정을 끝냅니다 [02:27].
  2. 초기 고객을 확보하세요 (Get Initial Customers): 제품을 사용하고 가치를 얻을 수 있는 최소한의 고객이라도 확보해야 합니다 [02:11].
  3. 사용자와 대화하고 피드백을 받으세요 (Talk to Users and Get Feedback): 완성된 제품이 아니라고 피드백을 무시해서는 안 됩니다 [02:57]. 고객이 원하는 것이 여러분이 생각하는 전체 제품(Full Thing)과 다를 수 있습니다 [03:19].
  4. 반복하세요 (Iterate):
    • 문제와 고객은 꽉 붙잡고(Hold Tightly), 해결책(제품)은 느슨하게 잡으세요(Hold Loosely) [03:24].
    • 솔루션이 문제를 해결할 때까지 반복적으로 개선해야 합니다. 잘 안 될 때 문제나 고객을 바꾸려고 '피벗(Pivoting)'하기보다는, 현재 솔루션을 고치는 '반복(Iterate)'을 우선해야 합니다 [04:11].

3. 린(Lean) MVP 구축
대부분의 경우 매우 **린(Lean)**한 MVP를 구축해야 합니다.
  • 빠르게 구축: 몇 주(Weeks) 안에 만들 수 있어야 하며, 몇 달(Months)이 걸려서는 안 됩니다 [04:39]. 랜딩 페이지와 스프레드시트로 시작할 수도 있습니다 [04:47].
  • 기능의 극단적인 제한: 초기 사용자에게 가장 중요한 문제(Highest Order Problems)에만 집중하고, 나머지 기능은 나중으로 미뤄야 합니다 [05:10].
  • MVP는 단지 **반복을 위한 기반(Base to Iterate From)**일 뿐입니다 [05:24].

4. 성공적인 MVP 사례
성공적인 기업들 역시 매우 단순하게 시작했습니다.
  • Airbnb (2008년): 숙소 이용 시 결제 기능이 없었으며 [05:54], 지도 보기도 불가능했고, 코드 작성자도 파트타임으로 일했습니다 [06:07].
  • Twitch (Justin TV 시절): 저스틴 한 명의 삶을 중계하는 단 하나의 채널만 있었고, 영상 해상도가 매우 낮았으며 [06:43], 대부분 비디오 게임 콘텐츠가 없었습니다 [07:12].
  • Stripe (SlashDev/Payments): 은행 제휴 없이 시작했으며, 기능이 거의 없었고, 심지어 창업자들이 고객 사무실에 직접 가서 통합을 도와주었습니다 [07:37].

5. MVP 구축을 위한 조언 (Hacks)
MVP를 매우 빠르게 구축하기 위한 실질적인 조언입니다.
  • 스펙을 시간으로 제한하세요 (Time Box Your Spec): 예를 들어, 3주 안에 출시하겠다고 정한 뒤, 3주 안에 만들 수 있는 것만 스펙에 포함하세요 [11:31].
  • 스펙을 기록하세요 (Write Your Spec): 스펙을 문서로 작성해야 변경할 때 자신이 무엇을 바꾸는지 명확히 알 수 있으며, 출시 지연을 방지할 수 있습니다 [11:49].
  • 스펙을 줄이세요 (Cut Your Spec): 출시 기한을 맞추기 어렵다면, 중요하지 않은 것들을 과감하게 잘라내세요 [12:26].
  • MVP와 사랑에 빠지지 마세요 (Don't Fall in Love with your MVP): MVP는 여정의 첫 단계일 뿐이며, 최종 비전과 다르게 끝날 가능성이 높습니다 [13:03].




문제를 정의하기 위해서 페르소나를 찾고, 

혹은 그 문제를 겪고 있는 사람을 찾아서 니즈를 확인한다.


인스타그램, 스레드, 오픈카톡방 등 적합한 컨텐츠의 반응을 확인해야 한다.

우선 사람을 모아야한다. 페르소나를 모을 수 있는 방법은 무엇일까?


초반부터 우리의 팬을 만들 수 있는 노력을 할 수 있어야 한다.


고객이 실망하더라도 괜찮다는 마인드셋을 갖춘다.


고객은 우리의 과정을 보고 싶어한다.


부디 흐지부지 창업가가 되지 말자.

한 가지 문제를 집요하게 파고들어야지, 이거 시도, 저거 시도 하다가 흐지부지된다.


우리만이 이 문제를 해결할 수 있다면 그 이유는 무엇이고, 그 솔루션은 무엇일까?





댓글

이 블로그의 인기 게시물

내배캠15일차_map() reduce() fold()

내배캠16일차_Flutter_스토어 앱 만들기

내배캠 [3주차 WIL]