Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

남궁혜민 7차시 과제 #8

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

hyeminililo
Copy link

MVVM 패턴을 이용하여, 파일을 나누어 상태관리 하는 날씨 앱 만들었습니다. getX와 provider와 같은 패키지를 사용하여 로그인 부분 상태관리, 날씨 정보를 저장할 수 있는 상태관리를 구현했습니다. 로그인을 하게 되면 현재 위치와 검색 창이 나오고 검색창에 검색을 통해 화면으로 이동하여 그 지역에 날씨 정보가 나올 수 있도록 화면을 구성했습니다.

Important content
MVVM 패턴으로 잘 나눠보려고 했는데, 아직은 미흡한 점이 많은 것 같습니다. 그리고 getX와 provider를 적절하게 사용했는지
에뮬레이터가 돌아가지를 않아, 실행을 하지 못해보고 과제를 제출했습니다. 수정해야하는 부분 알려주시면 바로 수정하겠습니다.

Question

  1. 애뮬레이터가 계속 무한대기로 떠서 UI를 저번 과제와 동일하게 하고 실행을 아직 못 해봤습니다. 저번 과제를 다시 열었을 에뮬레이터의 현재위치에 맞게 화면이 나오는데 로그인 화면을 제외하고는 무한 로딩만 하는데 해결하는 방법이 궁금합니다.

  2. MVVM 패턴이 익숙하지 않아, 이를 시작으로 코드를 나누어 보니 오히려 코드의 재사용이 많아진 것 같습니다. 코드를 간결하게 나타낼 수 있도록 파일을 잘 구성하는 방법이 궁금합니다.

Reference
개발하는 남자 ,, 코딩셰프,, 뤼튼 , 노션, 티스토리 참고했습니다.

Copy link
Collaborator

@cucumber99 cucumber99 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

어떤 식으로 상태 관리를 해야 할지 고민한 흔적을 엿볼 수 있었습니다.
Provider와 GetX가 곳곳에서 섞여있는 것을 보니 아직은 헷갈려 하시는 것 같네요.
스터디에서도 말했듯이, 쉽게 터득할 수 있는 것은 아니기 때문에 충분한 연습이 필요합니다.

추가로, MVVM이나 MVC 패턴 구현 난이도는 오히려 플러터에서 낮다고 생각합니다.
Model은 보통 데이터와 비즈니스 로직을 담당합니다. 해당 코드에서는 위치 정보, 위치 정보에 기반한 날씨 정보 등이 Model이라고 할 수 있습니다. 지금까지 해왔던 것 처럼 클래스로 관리합니다.
View는 플러터에서의 위젯입니다. View는 사용자에게 보여지는 영역으로, 사용자의 입력을 받거나 입력을 통해 얻은 결과를 보여줍니다.
View Model은 View의 상태를 관리하고 View의 비즈니스 로직을 담당합니다. View를 통해 받은 사용자의 입력을 Model로 전달하고, Model의 데이터를 View가 사용할 수 있는 형태로 변환합니다. View는 View Model을 참조하고 있으므로 View Model에서는 View의 상태를 관리해 주어야 합니다. 또한 View Model은 View에게 직접 데이터를 전달하지 않습니다. 여기서 사용하는 것이 바로 Provider, GetX와 같은 상태 관리 라이브러리입니다. (notifyListeners, update ... 등을 통한 상태의 변화 알림)

View에서 사용자의 입력을 View Model에게 전달하면, View Model은 Model로 데이터를 요청합니다. Model에서는 요청을 처리하고 View Model에게 전달합니다. 이렇게 되면 View Model을 참조(구독)하고 있는 View는 여러 상태 관리 기법에 의해 UI를 갱신하게 됩니다. 이 흐름을 잘 알고 있어야 합니다.

파일 구성 방법은 누군가에게 배운다고 해서 되는 것이 아닙니다. 우선 이러한 일련의 과정을 잘 파악하고 있어야 본인에게 편한 방법이 무엇인지 알 수 있습니다. 위에 서술했듯이 Model은 전부 클래스로 선언하지만, 상황에 따라 조금 더 세밀하게 설계할 수 있습니다. 가령 일반적인 Model은 데이터 설계 파일, 그리고 데이터를 가져오는 영역, 데이터 저장소 등으로 나눌 수 있겠습니다. 이 과제에서는 날씨 모델, 날씨를 가져오는 영역 등으로 나눌 수 있겠네요. 그리고 View는 일반적인 UI이므로 각 페이지, 위젯에 따라 나눌 수 있겠고 View Model은 ChangeNotifier를 상속받는 클래스를 생성하거나 혹은 GetXController를 상속받는 클래스를 생성할 수 있겠습니다. 이에 따라 프로그램을 설계해 보고 다른 사람들에게 피드백을 한 번 받아보세요.

Comment on lines +5 to +32
Widget? getWeatherIcon(int condition) {
if (condition < 300) {
return Image.asset(
'assets/images/weather_rain.png',
width: 100,
height: 100,
);
} else if (condition < 600) {
return Image.asset(
'assets/images/weather_snow.png',
width: 100,
height: 100,
);
} else if (condition == 800) {
return Image.asset(
'assets/images/weather_sunny.png',
width: 100,
height: 100,
);
} else if (condition <= 804) {
return Image.asset(
'assets/images/weather_cloud.png',
width: 100,
height: 100,
);
}
return null;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이전에도 언급했던 부분인데, else-if 구문은 굳이 사용할 필요가 없습니다.
else-if는 if 이후에 연속적으로 실행되는 것이 아니고 else를 먼저 거친 다음 실행되기 때문에 비효율적입니다.
번거롭겠지만 내부의 조건을 더 자세하게 걸면 if만으로 해당 코드와 같은 기능을 수행할 수 있게끔 할 수 있겠죠?

Comment on lines +24 to +30
try {
_weatherData = await _weatherLocation.getWeather(cityName);
setState(() {});
} catch (e) {
print(e);
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setState는 위젯 트리의 모든 부분을 갱신하므로 이 프로그램에서 사용한 Provider나 GetX를 적용시켜 보세요.
단순한 UI 변경을 위해서 사용한 것 같은데 라이브러리를 사용하는 방법이 더 편할 것 같네요.

Comment on lines +90 to +93
onPressed: () {
storageController.saveData(time.currentTime());
Get.to(() => StorageScreen());
},
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기서는 GetX의 라우팅 기능을 이용하였는데, GetX를 이용한 상태 관리나 종속성 주입을 제외한 다른 기능을 사용하려면 앱의 전체 부분을 GetMaterialApp으로 감싸 주어야 합니다.

Comment on lines +10 to +13
void saveData(time) {
this.time = time;
notifyListeners();
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

메소드의 이름을 조금 더 명확하게 지으면 좋을 것 같습니다.

Comment on lines +15 to +17
return ChangeNotifierProvider(
create: (context) => LoginToken(),
child: MaterialApp(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

최상위 위젯에서 전체를 ChangeNotifierProvider로 감싸줄 필요는 없습니다.
상태가 갱신되었을때 변경되어야 하는 위젯만 감싸주어도 됩니다.

Comment on lines +4 to +20
class StorageController extends GetxController {
final List<String> weatherList = <String>[].obs.toList();

void addWeather(String weather) {
weatherList.add(weather);
}

// 날씨를 지우는 함수
void removeWeather(String weather) {
weatherList.remove(weather);
}

//데이터 저장하는 메소드
void saveData(String data) {
addWeather(data);
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

obs는 변수를 관찰 가능한 Rx 타입으로 변환해줍니다. 하지만 여기서는 weatherList를 toList 메소드를 이용하여 일반적인 리스트로 변환하고 있습니다. 이는 변화를 직접 감지할 수 없습니다. 일반적인 리스트를 사용하고 싶다면 아래의 리스트를 변환하는 메소드들에 update 메소드를 추가하거나, 그게 아니라면 weatherList를 RxList로 선언해야 합니다.
final RxList<String> weatherList = <String>[].obs;

Comment on lines +30 to +34
Future<void> fetchCityName() async {
cityName = await locationModel.getMyCurrentLocation1();
weatherData = await locationModel.getWeather(cityName);
setState(() {});
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

위에서 언급한 것 처럼 setState 말고 GetX나 Provider의 기능을 사용해 보세요.

Comment on lines +29 to +43
body: GetBuilder<StorageController>(
builder: (controller) {
return ListView.builder(
// 콜백함수로 목록 항목 만들고 listTitke 위치 값 표시
itemCount: controller.weatherList.length,
itemBuilder: (context, index) {
return Card(
child: ListTile(
title: Text(controller.weatherList[index]),
),
);
},
);
},
),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

보통 GetBuilder는 Controller 내부에서 update 메소드를 이용해 변화를 직접 알려주었을 때 사용합니다.
StorageController에서 Rx 타입의 변수를 사용하려고 하신 것 같은데, 이 경우에는 Obx로 위젯을 감싸주면 자동적으로 UI를 갱신할 수 있습니다. 또는 GetX를 사용할 수도 있습니다.
Obx(() => ListView.builder(...)
GetX<StorageController>(...)

Comment on lines +24 to +26

final storageController = Get.put(StorageController());

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

StorageController의 인스턴스는 이미 storageScreen에서 생성되어 종속성이 주입되어 있습니다.
그러므로, Get.put을 통해 또 다시 인스턴스를 생성할 필요는 없습니다.
보통은 상위 위젯에서 Get.put으로 등록하고, 하위 위젯에서 Get.find로 등록된 컨트롤러를 사용합니다.
이렇게 여러 위젯에서 사용하는 컨트롤러는 보통 최상위 위젯에서 등록하는 것이 일반적입니다.
만약 최상위 위젯에서 등록하려면 먼저 GetMaterialApp으로 감싸고 initialBinding 옵션에서 선언하게 됩니다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants