Skip to content

[Step1] 1단계 UI-로직 분리하기 #5

New issue

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

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

Already on GitHub? # to your account

Open
wants to merge 2 commits into
base: malibinyun
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"

android {
compileSdkVersion 30
compileSdkVersion 31

compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
Expand All @@ -45,7 +45,7 @@ android {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.example.flip_card_game"
minSdkVersion 16
targetSdkVersion 30
targetSdkVersion 31
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
Expand Down
3 changes: 2 additions & 1 deletion android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
android:windowSoftInputMode="adjustResize"
android:exported="true">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
Expand Down
4 changes: 2 additions & 2 deletions android/build.gradle
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
buildscript {
ext.kotlin_version = '1.3.50'
ext.kotlin_version = '1.7.20'
repositories {
google()
mavenCentral()
}

dependencies {
classpath 'com.android.tools.build:gradle:4.1.0'
classpath 'com.android.tools.build:gradle:7.1.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
Expand Down
2 changes: 1 addition & 1 deletion android/gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-all.zip
65 changes: 65 additions & 0 deletions lib/flip_card_core.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import 'dart:async';

import 'asset_name.dart';

class FlipCardCore {
final _imageNames = [
AssetImageName.orange,
AssetImageName.banana,
AssetImageName.apple,
AssetImageName.strawberry,
];

final List<String> _randomImageNames = [];
int _frontCardCount = 0;
final List<int> _frontCardIndexes = [];

final StreamController toggleCardToFrontStream = StreamController();
final StreamController cardsMatchedStream = StreamController();
Copy link

Choose a reason for hiding this comment

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

StreamController를 사용할 때는 제네릭 타입을 명시해주는 것을 권장드려요!

final StreamController<bool> cardsMatchedStream = StreamController();


List<String> getRandomCardImages() {
return _randomImageNames.map((e) => e).toList();
Copy link

Choose a reason for hiding this comment

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

여기서는 map의 역할이 특별히 없는 듯 해요!
바로 _randomImageNames를 반환해줘도 무방할 듯 합니다.

}

void reset() {
// add 2 times
_randomImageNames.clear();
_randomImageNames.addAll(_imageNames);
_randomImageNames.addAll(_imageNames);

// shuffle
_randomImageNames.shuffle();
}

void flipCard(int cardIndex) {
_frontCardCount++;
_frontCardIndexes.add(cardIndex);
}

void checkCards() {
print('_frontCardCount : $_frontCardCount');
print('_frontCardIndexes : $_frontCardIndexes');

if (_frontCardCount == 2) {
toggleCardToFrontStream.add(true);
if (_frontCardIndexes.length >= 2) {
String firstCardName = _randomImageNames[_frontCardIndexes[0]];
String secondCardName = _randomImageNames[_frontCardIndexes[1]];
if (firstCardName == secondCardName) {
_randomImageNames[_frontCardIndexes[0]] = '';
_randomImageNames[_frontCardIndexes[1]] = '';

cardsMatchedStream.add(true);
}
}

_frontCardIndexes.clear();
_frontCardCount = 0;
Comment on lines +52 to +57
Copy link
Member Author

Choose a reason for hiding this comment

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

cardsMatchedStream.add(true); 를 주석 처리하면 아래 indexed와 count가 초기화 되는 것을 확인했어요.
stream 이벤트로 setState가 먼저 불려서 무언가 무시가 되는 것 같은데 해결 법을 전혀 모르겠네요.

처음 한 벌의 카드를 맞춘 뒤에 카드를 누르면, _frontCardCount는 3으로 초기화가 되지 않음을 확인했어요.

}
}

void dispose() {
toggleCardToFrontStream.close();
cardsMatchedStream.close();
}
}
151 changes: 59 additions & 92 deletions lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import 'package:flip_card/flip_card.dart';
import 'package:flip_card_game/asset_name.dart';
import 'package:flip_card_game/flip_card_core.dart';
import 'package:flutter/material.dart';

void main() {
Expand Down Expand Up @@ -31,37 +31,29 @@ class MyHomePage extends StatefulWidget {
}

class _MyHomePageState extends State<MyHomePage> {
final _imageNames = [
AssetImageName.orange,
AssetImageName.banana,
AssetImageName.apple,
AssetImageName.strawberry,
];

final List<String> _randomImageNames = [];
final FlipCardCore _flipCardCore = FlipCardCore();
Copy link

Choose a reason for hiding this comment

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

FlipCardCore를 별도의 클래스로 잘 분리해주셨습니다! 👏

다만 FlipCardCore는 데이터를 관리하는 클래스인 만큼, 여러 하위 위젯에서 공통적으로 사용될 가능성이 있어요!

1주차 때 배운 InheritedWidget이나 Provider를 사용하면,
나중에 하위 위젯들과 FlipCardCore를 공유하기 훨씬 쉬워질 것 같네요! 🙂

(시간 되실 때 보너스로 진행해보셔도 좋을 듯 해요! 😀)

final List<GlobalKey<FlipCardState>> _cardKeys = [];
int _frontCardCount = 0;
final List<int> _frontCardIndexes = [];

@override
void initState() {
super.initState();

print('initState');
_flipCardCore.toggleCardToFrontStream.stream.listen((_) {
_toggleCardToFront();
Copy link

Choose a reason for hiding this comment

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

카드가 2장이 뒤집혔을 때, toggleCardToFrontStream에 이벤트가 발생하는데요,
이벤트로 인해 _toggleCardToFront이 호출 되면,
_flipCardCount에는 어떤 영향을 미치는 지 확인해보시면 좋을 듯 합니다.

첫번째 매칭 후 카드가 더 뒤집히지 않는 이슈와도 연관이 있을 듯 해요!

});

reset();
}

void reset() {
// add 2 times
_randomImageNames.clear();
_randomImageNames.addAll(_imageNames);
_randomImageNames.addAll(_imageNames);

// shuffle
_randomImageNames.shuffle();
_flipCardCore.reset();

// create global key
_cardKeys.clear();
_cardKeys.addAll(_randomImageNames.map((_) => GlobalKey<FlipCardState>()));
_cardKeys.addAll(_flipCardCore
.getRandomCardImages()
.map((_) => GlobalKey<FlipCardState>()));
}

@override
Expand All @@ -70,66 +62,52 @@ class _MyHomePageState extends State<MyHomePage> {
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Wrap(
spacing: 4,
runSpacing: 4,
children: List.generate(
_randomImageNames.length,
(index) {
if (_randomImageNames[index].isEmpty) {
return Container(
width: 100,
height: 150,
color: Colors.transparent,
);
}
return FlipCard(
key: _cardKeys[index],
onFlip: () {
_frontCardCount++;
_frontCardIndexes.add(index);
},
onFlipDone: (bool) {
if (_frontCardCount == 2) {
_toggleCardToFront();

_checkCardIsEqual();

_frontCardCount = 0;

if (_frontCardIndexes.length >= 2) {
String firstCardName = _randomImageNames[_frontCardIndexes[0]];
String secondCardName = _randomImageNames[_frontCardIndexes[1]];
if (firstCardName == secondCardName) {
_randomImageNames[_frontCardIndexes[0]] = '';
_randomImageNames[_frontCardIndexes[1]] = '';

setState(() {});
}
}

_frontCardIndexes.clear();
_frontCardCount = 0;
body: StreamBuilder(
stream: _flipCardCore.cardsMatchedStream.stream,
builder: (context, snapshot) {
List<String> _randomImageNames = _flipCardCore.getRandomCardImages();
Copy link

Choose a reason for hiding this comment

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

StreamController와 마찬가지로 StreamBuilder를 사용할 때도 제네릭 타입을 명시해주는 것을 권장드려요!

StreamBuilder<bool>(
    stream: _flipCardCore.cardsMatchedStream.stream,
    builder: (context, snapshot) {
        // StreamBuilder의 제네릭 타입을 명시해주면, `snapshot.data`의 타입도 명확해집니다.
    },
)


return Center(
child: Wrap(
spacing: 4,
runSpacing: 4,
children: List.generate(
_randomImageNames.length,
(index) {
if (_randomImageNames[index].isEmpty) {
return Container(
width: 100,
height: 150,
color: Colors.transparent,
);
}
return FlipCard(
key: _cardKeys[index],
onFlip: () {
_flipCardCore.flipCard(index);
},
onFlipDone: (_) {
_flipCardCore.checkCards();
},
front: Container(
width: 100,
height: 150,
color: Colors.orange,
),
back: Container(
width: 100,
height: 150,
child: Image.asset(
_randomImageNames[index],
fit: BoxFit.cover,
),
),
);
},
front: Container(
width: 100,
height: 150,
color: Colors.orange,
),
back: Container(
width: 100,
height: 150,
child: Image.asset(
_randomImageNames[index],
fit: BoxFit.cover,
),
),
);
},
),
),
),
),
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: () {
Expand All @@ -152,20 +130,9 @@ class _MyHomePageState extends State<MyHomePage> {
}
}

void _checkCardIsEqual() {
print('_checkCardIsEqual');
print(_frontCardIndexes);
if (_frontCardIndexes.length >= 2) {
String firstCardName = _randomImageNames[_frontCardIndexes[0]];
String secondCardName = _randomImageNames[_frontCardIndexes[1]];
if (firstCardName == secondCardName) {
_randomImageNames[_frontCardIndexes[0]] = '';
_randomImageNames[_frontCardIndexes[1]] = '';

setState(() {});
}
}

_frontCardIndexes.clear();
@override
void dispose() {
super.dispose();
_flipCardCore.dispose();
}
}
Loading