[flutter] daily record-whitebrew3
12.1
websocket에서 보낼때, token확인해보자. [9:16 AM] local로 주소를 바꾸고 테스트해보자
hoyoul — 12/01/2023 9:33 AM test로 shared preference를 사용하지 않기로 한다.
hoyoul — 12/01/2023 10:06 AM 좀더 봐야할듯하다.
hoyoul — 12/01/2023 11:35 AM rails에서 user.auth_id_token_from_google에서 하는일은 내가 보낸 데이터를 꺼내서 user객체를 만드는데, 그 객체를 그대로 보내는게 더 낫고…물론 여기 보면 name은 제대로 세팅이 안됐다. 그리고. access token으로 google에 validation을 하는건 action을 봐야 알수 있을듯하다. return한 user객체에는 name이 제대로 설정되지 않은건 확인했다. (edited) [11:37 AM] 즉, token_to_json으로 보낼 데이터를 보면, user.email, user.name인데, 그냥 user객체를 다 보내면 되고, use.name은 ' [11:38 AM] ’ ‘값으로 되어 있는걸 고쳐야 한다. 꺼낸값을 세팅해야 한ㄷ. [11:39 AM] user객체가 가진, User id: 1, name: “”, cards: [], balance: 0.0, 여기에 email과 token을 합쳐서 보내야 한다. [11:41 AM] 내생각엔 validate하는거 없을듯 하고, 그냥 내가 보낸 token을 까서 설정해서 다시 보내는거 같다. 근데 여기서 name을 어떻게 까는지 확인해 보자.
hoyoul — 12/01/2023 11:47 AM “iss”=>"https://accounts.google.com ", “azp”=> “960869331915-ij7e0uhb6ai9gl4s06k6npb3irr8otvp.apps.googleusercontent.com”, “aud”=> “960869331915-ij7e0uhb6ai9gl4s06k6npb3irr8otvp.apps.googleusercontent.com”, “sub”=>“107394109020336430276”, “email”=>“hoyoul.park@gmail.com ”, “email_verified”=>“true”, “at_hash”=>“DYdFXKYo1W3Sw5Ownhz2bg”, “nonce”=>“kRjykpnywSgk4Ql8XVkbPQPTnzrgfiFqm8p1gkG43yM”, “name”=>“Hoyoul Park”, “picture”=> “https://lh3.googleusercontent.com/a/ACg8ocIOsRqhZjDS8zVpXRw83fEzaapqnQyb2_np2VT7KD8zk7A=s96-c ”, “given_name”=>“Hoyoul”, “family_name”=>“Park”, “locale”=>“en”, “iat”=>“1701398740”, “exp”=>“1701402340”, “alg”=>“RS256”, “kid”=>“e4adfb436b9e197e2e1106af2c842284e4986aff”, “typ”=>“JWT” [11:47 AM] 내가 보낸 token을 rails서버에서 확인해봤다. [11:49 AM] 하드코딩해서 google을 provider로 설정하는과정은 토큰을 까서 isss로 google이나 apple을 추출해서 설정해야지 하드코딩하면 안된다. [11:52 AM] handle_id_tokens()에서 처리하는거 같다.
hoyoul — 12/01/2023 11:55 AM 여기선 code로 확인하는게 낫다. 왜냐면 email로 db에서 찾고 없으면 record 만드는 부분이라 bp컨트롤이 짜증난다. [11:58 AM] def handle_id_token(params, provider, user) OmniAuthInfo.where(provider: provider, uid: params[“sub”]).first_or_create do |auth| auth.provider = provider auth.uid = params[“sub”] # The unique ID of the user’s Google Account auth.email = params[“email”] auth.first_name = params[“given_name”] || params[“name”] auth.last_name = params[“family_name”] auth.photo = params[“picture”] auth.user = user if auth.user_id.blank? end end [11:58 AM] 이코드에서 처리한다. [11:59 AM] 여기선 firstname하고 lastname도 있다. [12:01 PM] 여기에선 제대로 저장될텐데? [12:01 PM] 그 다음에 어딘가에서 name설정을 세팅 안해준건데…
hoyoul — 12/01/2023 12:05 PM 근데 name은 어차피 flutter에서 받지 못하고 server에서 ‘‘로 보낸준거 확인했으니까, websocket이 왜 안되는지 확인해보자.

Figure 1: name1
내가 받은 메시지는 위와 같은 메시지였다. [1:45 PM] bp를 걸고 확인해봐야 겠다. 우선 연결만 확인하자. [1:50 PM] connection맺을때 두개의 token모두 테스트했을 때 모두 에러가 났다. postman을 보자.

Figure 2: name2
channel을 초기화하는 코드와 사용하는 코드가 분리되어 있는데, 둘중 하나에서 문제가 난건 확인했다. [2:08 PM] 간단하게 생각하면 channel을 별도로 떼어내고 getter를 하나 만들면 되지 않을까 한다. [2:09 PM] null이 나왔다면 이해가 쉽게 될텐데…null이 아닌게…좀 그렇긴 하다. 여튼 channel을 별도로 service 클래스에서 꺼낼수 있게 하자.
hoyoul — 12/01/2023 2:24 PM channel을 return해도 안된다. [2:27 PM] 연결하고 바로 받게 해보자.
hoyoul — 12/01/2023 2:35 PM 코드 자체는 간단하다. * web socket test * const wsUrl = ‘ws://kholdem.fly.dev/websocket’; / server test / const wsUrl = ‘ws://localhost:3000/websocket’; // local test
// String token = tokenExpiration.token!; ; String token = ’eyJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6ImVuZ2luZWVyZWQuZGVzaWduQGdtYWlsLmNvbSIsImV4cCI6MTczMjc4NjE5OH0.WVi4yn3PZJLhHd5i_-fgfsvmaY6o—deILl5ZGFwac’; Map<String, String> headers = { ‘Authorization’: ‘Bearer $token’, };
final webSocketService = WebSocketService(wsUrl, headers);
_/ IOWebSocketChannel channel = webSocketService.getChannel();
/_ webSocketService.checkWebSocket(); / webSocketService.subscribeGameRoom(); / webSocketService.joinGameRoom(); [2:36 PM] url과 header를 만들고, 그것을 webSocketService에 넘겨준다. 그러면 webSocketService에서, [2:36 PM] WebSocketService(String url, Map<String, String> header) { channel = IOWebSocketChannel.connect( Uri.parse(url), headers: header, );
channel.stream.listen((message) {
print('Received from server: $message');
});
} [2:36 PM] connection맺고 바로 message듣는다. [2:37 PM] 이건 아주 간단하게 테스트만 하기 위해서 기존 코드를 다 제거했다. [2:37 PM] 보기 쉽게하려고 try catch도 빼고 그냥 연결하고 바로 listen을 bp로 체크하기 위해서 가장간단하게 했는데.. [2:38 PM] bp로 확인된건, channel생성에 문제가 있어보인다. [2:38 PM] 위와 같이 사용하는게 문제가 있지 않고서야..syntax문제는 아닌데… [2:39 PM] 문서를 다시 봐야겠다. [2:41 PM] 문서에는 별로 대단한건 없는데, closeReason이란게 있는데…이것도 출력해보자.그리고 status를 check할수도 있기 때문에 try catch와 status check를 다시 해보자. [2:41 PM] 그리고 connect에 문제가 있다. [2:42 PM] head를 이렇게 추가하는게 아닌거 같다.
hoyoul — 12/01/2023 2:42 PM 근데 왜 에러가 안났지? [2:46 PM] 근데 이상하긴 이상하다. 왜냐면, 이전에도 말했지만, rest api는 토큰을 url에 넣거나 html본문에 넣을수 없다. 즉 get방식을 해서 url에 token을 넣을수도 없고, post로 html본문에 넣을수도 없다.따라서 header에 넣을수 밖에 없다. [2:47 PM] url에 넣는다는건 http://sdafasdfasdf.asdfas.com/?token=sadfasdfasdf 이렇게 직접 노출하는거라서 get방식도 무조건 header에 token을 넣어야 한다. [2:48 PM] post는 html문서 body에 token을 넣는거라서 이것도 직접 노출되기 때문에 header에 넣었다. [2:48 PM] 그런데 websocket은 socket이기 때문에 payload에 넣어야 한다. 애초부터 header에 넣을 생각을 하지 않는데 넣는거다.
hoyoul — 12/01/2023 2:50 PM 좀 이상한 처리인데…그래서 websocket 클래스에는 header 자체를 처리하지 않는다. 내가 header라고 쓴건, rest api처럼 그냥 했더니 된건데… [2:50 PM] https://pub.dev/documentation/web_socket_channel/latest/web_socket_channel/WebSocketChannel/WebSocketChannel.connect.html
여기도 header를 처리하지 않는다. protocol이고 이게 payload다. [2:51 PM] chat gpt한테 물어보자.

Figure 3: name3
그렇다. 내말이 맞다. [2:52 PM] 이게 정상이지. [2:53 PM] gpt가 알려준 방법은 url에 token을 보내는 get방식인데 이건 안된다. [2:54 PM] 나같은 경우는 Authorization: token으로 보내라는 뜻같다. [2:54 PM] 근데 web + socket에서 web은 http프로토콜을 얘기하기도 한다. [2:54 PM] 그래서 100% 잘못된 방식이냐? 또 그건 아니다. [2:55 PM] 그냥 header에 보내도 문제는 없다고 생각이든다. [2:55 PM] 아…이랬다 저랬다..하는거 같은데… [2:55 PM] 근데 web + socket이라서…http도 사용하니까…header에 bearer로 보내는게 큰 문제인거 같지도 않다. [2:55 PM] 내가 socket입장에서 너무 본건데…. [2:56 PM] web socket이니까….그냥 header에 보내도 틀린건 아닌거 같은데… [2:57 PM] 여튼 url로 보내보긴 하는데, header에 넣을수 있는 뭔가가 있을것도 같은데…
hoyoul — 12/01/2023 2:58 PM 위에 gpt chat내용이 끊겼다.

Figure 4: name5
websocket자체 api에서는 http헤더를 직접적으로 수정하거나 설정하지 못한다면 header에 bearer:token은 집어넣을수 없고, get방식으로 url에 포함시켜서 전달하라는건데… [3:00 PM] 이건 노출이되서 아닌거 같고…
hoyoul — 12/01/2023 3:06 PM 원래 socket을 사용하면 payload에 protocol로 token: 값을 넣어서 보내면 server에서 token을 비교해서 진위여부를 check하는게 일반적이긴 하다. socket으로 하는경우는 다그렇게 했는데, 이게 web socket이다 보니, http헤더에 넣는 방식도 가능해보이긴 하는데, 우선 gpt는 없다고한다. url에 넣어서 보내는 get방식은 쓰면 안된다. [3:06 PM] 근데 url방식 해보자.
hoyoul — 12/01/2023 3:13 PM url로 하니까, 문법에러가 계속 나네…내가 사용하는 web_socket_channel package말고 다른 package에서 가능한지 보자. [3:18 PM] 없다. [3:18 PM] websocket 패키지중 [3:18 PM] https://pub.dev/packages/websocket/install
이것은 incompatible다.
hoyoul — 12/01/2023 3:23 PM 근데 난 gpt의 답이 틀렸다고 생각한다. [3:24 PM] 이게 socket이면 data payload에 싣는게 맞다. 근데 web scoket이기때문에 http를 컨트롤 못한다? 수정못한다…이게 말이 안되는데…. [3:24 PM] https://pub.dev/documentation/web_socket_channel/latest/web_socket_channel/WebSocketChannel/WebSocketChannel.connect.html
그리고 공식문서도 웃긴게 connect에선 protocol밖에 기술되지 않았다.
hoyoul — 12/01/2023 3:30 PM 10분만 쉬자.
hoyoul — 12/01/2023 3:37 PM postman을 다시 보자. 어떻게 server에서 반응하는지도 보자. postman은 break못걸지만, rails는 걸수 있기 때문에…어떤 차이가 있나? 들어오는 socket에 어떤 차이가 있는지 확인해야 해결의 실마리를 얻을수 있을것이다. [3:44 PM] 이것도 테스트 하기 어려운게, local에서 하면 http연결하고난후에 websocket을 연결하는 구조라..ㅋ
hoyoul — 12/01/2023 4:52 PM 우선 actioncable은 header를 수정할 수 있다. [4:52 PM] https://pub.dev/packages/action_cable
그럼 설치를 하자. [4:53 PM] flutter pub add action_cable [4:55 PM] 충돌이 있는지 확인하자. [4:55 PM] dio와 lints에 dependency가 있다. [4:56 PM] 이게 충돌이라고 하긴 그렇지만, 여튼 의존성이 있어서 문제 있을 수 있다. [4:57 PM] 이게 actioncable을 깔아서 dependency가 있는건 아닌거 같다. 단순히 update하라는거 같은데… [4:58 PM] 여튼 package를 설치하면 dependency가 생기니까…그것에 맞춰서 update는 해주기로 한다. [4:59 PM] 레일즈의 경우는 dependency가 문제되면 제대로 동작 안되는 문제가 늘 있어와서 플러터도 비슷하지 않을까? [4:59 PM] flutter pub upgrade dio
hoyoul — 12/01/2023 4:59 PM 로 업그레이드 하자.
12.3
login처리가 안된다. [11:58 AM] bp를 걸어보자. [11:58 AM] 서버가 코드 변경됐나?

Figure 5: server-error1
보내는건 예전과 동일하고, response를 받지 못한건데… [12:16 PM] local에서도 에러가 나는지 확인하자. [12:19 PM] 이틀전에 코드가 변경됐지만, name관련 코드라서 이게 영향은 없어보이긴 하는데…bp를 걸어서 확인해야 할듯 하다. [12:19 PM] 근데 response 500error가 뭔지 모르겠다. [12:20 PM]

Figure 6: server-error2
서버가 예상치 못한 상황에 직면해서 요청을 완수하지 못한…뭐 이런뜻 같은데..
hoyoul — 12/03/2023 12:37 PM local 서버에서 bp를 걸고, 내가 보낸 값이 제대로 도착하는지 controller에서 확인해봤다.

Figure 7: server-error3
제대로 server에 도착하고, server에서 google에 요청도 하고 결과값도 얻었다. [12:39 PM] 그러면 어디서 에러가 나는지 tracing해보자. [12:40 PM] 엥 200 ok 가 정상적으로 return된다.

Figure 8: server-error4
flutter도 정상이다.

Figure 9: server-error5
그러면 rails가 local과 server가 다르다는건데.. [12:47 PM] 그럴리는 없을텐데… [12:47 PM] 내가 변경한건, url밖에 없는데…그렇다면 내 url이 잘못된거 아닌가? [12:47 PM] // String httpUrl = ‘https://kholdem.fly.dev/auth/google_oauth2_with_id_token'; String httpUrl = ‘http://localhost:3000/auth/google_oauth2_with_id_token’; [12:48 PM] http로 해야 했어야 하는데, https다. [12:48 PM] 근데 왜 지금껏 잘됐지? [12:48 PM] http로 바꾸고 테스트 해보자.

Figure 10: server-error6
이번엔 301에러가 난다. [12:54 PM] 즉 이건 server와 github의 소스가 다르고, 서버에서 직접 작업하는듯하다. [12:54 PM] 지금 작업중인듯하다. [12:57 PM] https에서 500에러는 https라서 난거라고 하자. [12:58 PM] http에서 301에러는 서버에서 뭔가 작업을 했지만, github에는 반영을 하지 않은거라고 보면 된다. [12:58 PM] 그래서 local과 server가 다른듯하다. [12:59 PM] 그럼 local에서 name은 제대로 response하는지 확인 해보자.
hoyoul — 12/03/2023 1:23 PM name은 제대로 안됐다. 코드를 보니, 반영 안됐다. [1:25 PM] 5분이면 고칠거 같은데.. [1:25 PM] local에서 해볼까.. [1:28 PM] 괜한짓 하지 말자.
hoyoul — 12/03/2023 1:36 PM 왜냐면 이게 구조가 잘못된거라서… [1:38 PM] 여튼 local에선 web socket은 test할 수 있을테니 websocket이나 test하자.
hoyoul — 12/03/2023 3:39 PM An unauthorized connection attempt was rejected [3:39 PM] actioncable을 써도 똑같다. [3:39 PM] connection자체가 안된다. [3:45 PM] 그리고 token을 하드코딩하면 안된다.
hoyoul — 12/03/2023 3:50 PM rails에서도 bp걸고, flutter에서도 bp걸고…이렇게 두개를 다 디버깅하면 시간이 많이 걸린다. [3:51 PM] bp걸면 원인도 알겠지만, 노가다다. [3:52 PM] 눈만 아프다.
hoyoul — 12/03/2023 4:31 PM 근데 이건 어차피 구현될 수 밖에 없는거긴함. [4:32 PM] web socket에서 header에 넣어서 보내는거고 header를 받아서 처리하는것도 거의 정형화된거라서…시간은 걸리지만 안되거나 [4:33 PM] 못하는건 확실히 아니다. [4:33 PM] 안되는 거면 모르겠지만, 되는건 확실하기 때문에…test해서 원인 알면 고치면 되는거다. [4:34 PM] 다만 피곤하다.
hoyoul — 12/03/2023 9:29 PM 연결 성공했다. [9:30 PM] 서버는 현재 좀 이상하다. local에서 성공했다. [9:30 PM] 내일 subscribe와 join test하자. December 4, 2023
12.4
subscribe를 해보자.
</img/oauth/subscribe.mp4>
subscribe는 확인했다. [10:25 AM] join은 연결만 확인하자. 어차피 데이터를 return받진 못한다. locala에 db가 비어있어서 아니면 faker인가? 가짜데이터 만들어주는 gem이 있다. [10:25 AM] 그거 사용해서 join까지 해야지…action은 다 할 수 있다고 확인이 되는거라서… [10:26 AM] subscribe는 데이터를 주고받는게 아니다. [10:26 AM] 여튼 10분만 쉬자. [10:27 AM] 그리고 join이 성공하고 data를 가져와야 2 card play가 보이게 해야 한다. [10:27 AM] 지금은 test라서 그냥 subscribe하고 화면 보이게 했다.
hoyoul — 12/04/2023 11:03 AM faker는 이미 gemfile에 있어서 사용하면 된다. emacs에서 rails navigating 바인딩이 이상하다. 좀 바꾸자.
hoyoul — 12/04/2023 11:35 AM faker를 사용해서 record를 만들려면, 필요한 record와 model의 관계를 알아야 하는데, [11:36 AM] model코드에서 관계를 보기가 힘들다. db table로 관계를 추론하는게 더 쉬워 보인다. [11:36 AM] 우선 GameRoom은 field가 하나다. [11:36 AM] User하고 relation이 있는데, [11:36 AM] 다대다 관계로 만들었다. [11:37 AM] 그래서 association table이 필요한데, 그게 gameRoomsUser다. [11:38 AM] 즉 game에 참여하고 있는 user를 count할려면, gameId를 던져주고, gameId에 해당하는 user를 count해서 방에 몇명있는지 확인하는듯하다. [11:39 AM] 내가 구조가 잘못되었다고 하는건, User와 Game에 해당하는 속성들이 다른데 다 있기 때문이다. [11:39 AM] Game이 아니라, GameRoom, Game은 아직 안봐서 모르겠다. [11:40 AM] gameRoom이나 User라는 class를 설계하면 포함되는 속성들을 모두 포함시키고 그 다음 속성을 따져서 관계설정을 하는데.. [11:40 AM] 이런 구조가 아니다. [11:41 AM] RDB로 table설계를 하는것고 oop로 프로그램을 만드는 설계는 다 비슷하다. 그래서 or mapping이 나왔고..즉 같기때문이다.
hoyoul — 12/04/2023 11:42 AM 여튼…GameRoom은 하나의 field인 state가 있고, User는 이미 faker에 만든게 있는듯하다.
hoyoul — 12/04/2023 11:55 AM 그러면 GameRoom만 faker로 데이터를 넣고 테스트 해보자. [11:57 AM] gr1 = GameRoom.create
u1 = User.create(name: “John Doe1”, balance: 3_000_000, email: “user1@test.com ”) u2 = User.create(name: “John Doe2”, balance: 4_000_000, email: “user2@test.com ”) u3 = User.create(name: “John Doe3”, balance: 6_000_000, email: “user3@test.com ”) u4 = User.create(name: “John Doe4”, balance: 8_000_000, email: “user4@test.com ”) u5 = User.create(name: “John Doe5”, balance: 5_000_000, email: “user5@test.com ”)
gr1.game_room_users.create(user: u1) gr1.game_room_users.create(user: u2) gr1.game_room_users.create(user: u3) gr1.game_room_users.create(user: u4) gr1.game_room_users.create(user: u5) [11:57 AM] 이거 보면 u1-6까지 user만들고, gameRoomUser인 associate table 을 사용해서 game방에 있는 user를 만드는 fake같긴 하다. [11:58 AM] 그래도 사용해도 될듯하긴 한데… [11:58 AM] 뭐 해보자.

Figure 12: db test1
만들어졌다.
hoyoul — 12/04/2023 12:02 PM 그럼 join을 날렸을때 어떤일이 벌어지는지 channel을 좀 보자. [12:02 PM] def join
@game_room = GameRoom.available.first || GameRoom.create @game_room.join(current_user)
stream_for @game_room
broadcast_to @game_room, \_serialize_game_room(@game_room)
end (edited) [12:03 PM] faker를 했을때, 만든 방은 1개인데, 5명이 있다. 그런데 방상태는 standby로 되어 있긴한데… [12:05 PM] GameRoom.create로 방을 만들고, 그러면 2번방이 만들어지고 join하면 game_room_user에 2번에 내 id가 포함된 record가 만들어질꺼다. [12:05 PM] 그리고 broadcast하면 나밖에 없으니까, 나한테 어떤 message가 올것이다. [12:07 PM] 내가 기대하는 message는 seat에 4라는 값과 대기자의 수를 기대하는데 모르겠다. 왜냐면 그 값이 사용되기 때문이다.
hoyoul — 12/04/2023 12:26 PM join은 cable.performAction( “GameRoomChannel”, action: “join”, channelParams: {}, actionParams: {} ); [12:26 PM] 이렇게 보내면 되지 않을까? [12:30 PM] 밥먹고 오자.

Figure 13: db test2
서버에서 다음과 같은 에러가 발생했다.
hoyoul — 12/04/2023 2:26 PM 이 문제는 server에서 처리가 안된다는것까진 확인했다. [2:27 PM] 그런데 server에서 action cable을 제대로 사용하고 있는지를 확인해야 하는데, actionCable을 내가 잘 모른다. [2:28 PM] 그냥 공부해서 해결하면 되긴 하다. [2:33 PM] 우선 난 broadcast가 잘못되었다고 보고 있다. 그런데 action cable을 좀 알아야 한다. 예전에 공부한적이 있지만, 다 까먹었다.
hoyoul — 12/04/2023 2:47 PM 우선 지금은 아예 채널 등록이 안되는 상황인데… [2:48 PM] 채널만 등록하자. 근데 이게 내가 생각하는 문제를 해결하는 해결법은 아니다. 다만 지금 상황에선 rails에서 join에 bp가 걸리지 않는 상황이기 때문에 [2:49 PM] join요청에 대한 bp가 걸리게 해야 한다. 이것은 channel을 등록해서 처리하면 되지 않을까 하는 생각이다.
hoyoul — 12/04/2023 3:03 PM 우선 channel을 등록해서 join을 실행하게는 했다.

Figure 14: channel1
join을 실행하게 해서, 2번재 방을 만드는 코드를 실행하게 만들기는 했다. [3:07 PM] 그런데 이 데이터를 client에 보내는게 의미가 있는지 모르겠다. [3:08 PM] 보낼려면, 남은 seat정보 other waiting의 수를 보내고 active한, 혹은 standby한 정보를 같이 보내야 의미가 있지… [3:10 PM] other waiting에 대한 구현도 되어 있지 않고, 빈자리에 대한 계산은 쉬우니까 계산하고 같이 던져주면 되는데, 던지는게 객체다. 근데 객체 지향적 설계를 하지 않았기 때문에 던지기도 쉽지 않다. [3:10 PM] 구조가 oop구조를 사용하지 않기 때문에 생기는 문제다.
hoyoul — 12/04/2023 3:11 PM rails는 oop로 짜는 서버라서, jsp나 php를 해봤던 사람에게 편한거지….jsp나 php같은것을 안해봤으면 굉장히 어려운 framework이다. [3:11 PM] 다 생략이 되어 있기 때문이다.
hoyoul — 12/04/2023 3:30 PM flutter에서 메시지를 받는게 좀 이상하다. [3:31 PM] 우선 객체가 protocol이다. 이말은 client에도 동일한 객체가 우선 있어야 한다. [3:33 PM] 보내는게 다음과 같은 형태다. Broadcasting to game_room:Z2lkOi8va2hvbGRlbS1zZXJ2ZXIvR2FtZVJvb20vMg: “{\“data\”:{\“id\”:\“2\”,\“type\”:\“game_room\”,\“attributes\”:{\“state\”:\“standby\”},\“relationships\”:{\“current_game\”:{\“data\":null},\“users\”:{\“data\”:[{\“id\”:\“1\”,\“type\”:\“user\”}]}}},\“included\”:[{\“id\”:\“1\”,\“type\”:\“user\”,\“attributes\”:{\“name\”:\”\”,\“balance\”:\“0.0\”,\“ca… [3:35 PM] 이게 gameRoom이란 class를 만들어야 하고 member변수로 current_game, included로 user가 있다. oop로 설계가 되지 않는 program에선 server에서 이렇게 그냥 던지면 client에서 해결해야 하는 hell이 되는데…
hoyoul — 12/04/2023 4:04 PM 그런데 class를 만들기전에 먼저 서버의 data가 와야 하는데 안온다. [4:04 PM] 단순히 class가 없다고 안 오는 것은 아닐것이다.
hoyoul — 12/04/2023 4:41 PM 우선 package를 보면 https://pub.dev/packages/action_cable
여기서 stream을 받을 함수는 OnMessage가 있다. 근데 받질 못한다.그리고 옆에 document도 있는데, 거의 없다고 보면 되고…그러면 막상 도움될만한게 없다. [4:42 PM] 그러면 까면 된다. 내부 구조를 보면 감이 잡히는데, [4:43 PM] action_cable.dart라는 파일이 그 구현체다. [4:44 PM] void performAction(String channelName, {String? action, Map? channelParams, Map? actionParams}) { final channelId = encodeChannelId(channelName, channelParams);
actionParams ??= {}; actionParams[‘action’] = action;
_send({ ‘identifier’: channelId, ‘command’: ‘message’, ‘data’: jsonEncode(actionParams) }); }
void _onData(dynamic payload) { payload = jsonDecode(payload);
if (payload[’type’] != null) { _handleProtocolMessage(payload); } else { _handleDataMessage(payload); } }
코드는 길어서 다 안올라가는데, 보면 On_data가 있다. [4:44 PM] 이게 내부 stream이다. [4:45 PM] 이것은 ActionCable로 객체를 선언하면 받을수 있을것 처럼 생겼다. 테스트 해볼수도 있다. [4:45 PM] 아니면 수정해버려도 되는데…여튼 그렇다. [4:46 PM] 그리고 ActionCable은 내부에서 _/ rails gets a ping every 3 seconds socketChannel = IOWebSocketChannel.connect(url, headers: headers, pingInterval: Duration(seconds: 3)); _listener = _socketChannel.stream.listen(onData, onError: () { this.disconnect(); / close a socket and the timer if (this.onCannotConnect != null) this.onCannotConnect!(); }); _timer = Timer.periodic(const Duration(seconds: 3), healthCheck); } [4:46 PM] IOWebSocketChannel을 사용하는데, 이게 web_socket_channel package다. 즉 내가 처음에 사용했던 package다. [4:47 PM] 옛날 package는 header를 설정할 수 있었다. 그런데 version이 올라가면서 이부분이 없어진거다. [4:47 PM] 즉 낮은 version의 web_socket_channel package를 사용하면 action_cable을 안사용해도 되는거다.
hoyoul — 12/04/2023 4:49 PM _ onData를 사용하려면, 함수를 상속받아서 overriding하라는 소리로 들린다. 그런데 보통 package를 제공할때 아주 초창기 java방식이 아니고서는 그냥 바로 사용하게 하는게 어떻게 보면 표준인데…모르겠다. getX도 package로 만든걸 보면, 아직 flutter는 초창기란 느낌이 든다.
hoyoul — 12/04/2023 5:00 PM 정확히는 모르겠지만, 해보고 싶은건, onMessage: (Map message) {} 이걸 사용하고 싶긴 하는데, 우선 bp에 안걸린다. 이말은 stream이 flutter까지 안온다는 말이다. [5:01 PM] action_cable이 아닌 web_socket_channel을 써야 connection관리가 될듯하다.
hoyoul — 12/04/2023 7:39 PM 위에서 말한 on_Data()는 접근이 안된다. [7:41 PM] onMessage는 안되는데, 만일 server에서 {‘key’: ’test’}를 보내보자. 그리고 client에서 받는지 확인해보자. 간단한 map을 보내고 받는게 가능한지 확인하고 싶다.
hoyoul — 12/04/2023 7:53 PM stack overflow에 actioncable과 flutter사용법이 있다. https://stackoverflow.com/questions/66392838/how-to-connect-flutter-to-rails-6-1-action-cable-websocket
내가 한 방법하고 비슷하지만, 눈여겨 볼게 하나 있긴 한데… [7:56 PM] broadcast방법이긴 한데…이게 이렇게 하면 channel등록자한테 다 가기 때문에, 내가 찾는 방법과 다르다. [7:56 PM] 난 일부사람들한테만 가게 만들고 싶기 때문이다. [7:59 PM] 또한 onMessage함수를 disconnect message라고 comment를 달았는데…그러면 message주고 받는건 어떻게 한다는 건가?
hoyoul — 12/04/2023 8:00 PM message통신할 때, 발송과 도착은 원래 순서가 뒤죽박죽일텐데, 어떤식으로 순서를 맞춰서 조립하는지…그것도 궁금하다.
hoyoul — 12/04/2023 8:14 PM comment는 뒤에 달아서 햇갈렸고, 위 예제는 우선 investigation을 사용해서 channel을 만들어서 사용하는데, 주고 받을때 id값을 부여해서 그 값으로 통신하는데, 중요한건 investigation이란 class가 공개가 안되어있다. [8:17 PM] 내가 눈여겨 본건, 객체 stream과 channel stream을 분리해서 처리한 부분이다. [8:18 PM] 이게 나한테 적용가능한지 궁금하다.
hoyoul — 12/04/2023 9:42 PM def join
@game_room = GameRoom.available.first || GameRoom.create @game_room.join(current_user)
stream_for @game_room
broadcast_to @game_room, \_serialize_game_room(@game_room)
[9:43 PM] 이게 우리 코드의 action인데, 우선 이 action을 바로 수행할 수는 없다. [9:44 PM] channel stream의 연결이 된 이후에 client에서 channel stream을 인자로 넣어서 join action을 수행하기 때문이다. [9:44 PM] 그래서 subscribe에서 channel stream을 만들어줘야 한다. [9:45 PM] 그리고 처음에 분석할때 별 문제 없다고 생각한게 broadcast_to @gamerom, _serialize 코드인데. [9:45 PM] def _serialize_game_room(game_room) options = { include: [:current_game, :users] }
GameRoomSerializer.new(game_room, options).serializable_hash.to_json
[9:46 PM] 코드만 보면, 방안에 있는 사람에게만 broadcasting한다는건데, 나도 그렇다고 생각했다. [9:46 PM] 그런데, googling하면서 느낀건 이게 맞는것일까? 하는 생각이 들었다. [9:47 PM] 100명의 사용자가 접속할때, 100명의 사용자를 서버에서 관리하는 방식은 여러가지가 있다. [9:49 PM] customID가 많이 사용되고, 이 방식은 stack overflow의 예제에서도 사용되었다. 그리고 current_user라는 객체로 현재 접속자의 정보를 rails에서는 알수 있다. 이 객체의 unique한 값으로 관리할 수 도 있다. 위에서는 관리를 하지 않고, seriazable_game_room이라는 함수에서 처리를 한다. 보기에는 그럴듯하다. [9:49 PM] 그런데 테스트해봤을까?
hoyoul — 12/04/2023 9:49 PM 안해봤을꺼 같다. [9:53 PM] broadcast_to의 기본동작은 channel stream에 가입한 모든 사용자에게 broadcast한다. stackoverflow에서도 아니 다른 tutorial에서도 이런 방식을 사용하지 않기 때문에 나는 해봐야 안다는 것이고, 아침에 코드를 봤을때는 broadcast를 channel stream의 모든 사용자에게 보내느데 있어서 serialize라는함수로 제약을 걸기 때문에 가능하다고 생각했는데…구글링해서 본 코드들과 차이가 있기 때문에 확신은 안선다. 해봐야 안다는것이다. [9:53 PM] 여튼 그런 생각을 했었고… [9:55 PM] 우선 name리턴 안되는 문제도 테스트를 안하기 때문에 모르는거 같다. 보내기전에 rails실행하고 client로 보내기전 puts로 name을 찍어보면 아무것도 안 찍히는건 바로 확인이 가능하다.
hoyoul — 12/04/2023 9:57 PM 여튼 오늘은 여기까지 하자
12.5
action cable performance issue [9:20 AM] https://medium.com/geekculture/actioncable-to-anycable-c9d9d54665f0
action cable이 속도문제가 있다고 한다. broadcasting때 현저한 느림이 있어서 rails5이후로는 anycable로 바꾸는 추세. [9:21 AM] 위 article은 바꾸는 방법에 대해 나왔다. [9:21 AM] action cable tutorial [9:22 AM] actioncable을 정확하게 사용하는 법을 모르겠다. [9:22 AM] tutorial이나 동영상이라도 하나 봐야 겠다. [9:23 AM] https://youtu.be/KNJeqpnqOhQ?list=PLdo16AWp70d2uMPyzO4bVoeR15rsJc32S
youtube검색시 제일 처음 보인다. 그나마 최근 영상인거 같다. 한번 살펴보자.
hoyoul — 12/05/2023 10:23 AM 여긴 action cable을 직접 사용하는 방식을 보여주지 않는다. chatting을 사용해서 messsage를 주고받는데, 이때 사용되는 핵심이 message.display인데, 여기서 broadcast를 사용하는지 몰라도, 설명은 없다. 이미 있는 repo를 실행해서 동작을 확인하는걸 보여주는 영상이라서 의미가 없다. [10:24 AM] 내가 원하는건, flutter에서 websocket사용하는 방법에 대한 명확한 이해, rails에서 web socket사용하는 것에 대한 명확한 이해다. [10:25 AM] rails부터 찾아볼려니 너무 옛날이다. [10:26 AM] flutter가 최신 자료가 많을테니 flutter부터 우선 명확하게 이해할 필요가 있다. [10:26 AM] https://www.youtube.com/watch?v=Q8O1XyKuA38
우선 제일먼저 뜨는걸 보자.
hoyoul — 12/05/2023 10:33 AM 진짜 simple하다. 그런데 이것은 server를 고려하지 않은 처리다. 즉 connect하고 message를 받는것만 얘기한다. 이미 아는거다.

Figure 15: action cable1
action cable repo에 있는 issue인데, 나같은 애가 질문 올렸다. 답변은 없고, 좀 비활성화된 repo같다. [10:41 AM] 답변을 안하는 이유는 2가지가 있을수 있다. 너무 뻔해서, 아니면 관리를 안해서…근데 2020년 질문에 답변이 없는건, 관리를 안한다고 보는게 맞을듯 하다.
hoyoul — 12/05/2023 10:51 AM https://www.youtube.com/watch?v=Aut-wfXacXg
이게 그나마 도움이 될듯 하다. flutter를 사용하고, express 서버를 사용한다.
hoyoul — 12/05/2023 12:17 PM flutter에서 사용하는 action_cable package를 사용하던, web_socket_channel package를 사용하던, 내 방식에는 문제가 없어보인다. [12:17 PM] 그러면 rails에서 join에 접속은 확인했고, response를 아주 간단한것을 만들어서 test해보자. [12:18 PM] 과연 간단한 데이터도 내가 받지 못하는것일까?

Figure 16: action cable2
간단한 broadcasting을 서버에서 구현하고, flutter에서 받았다. 정상적으로 된다. [12:36 PM] 그럼 원인이 무얼까? 원인은 간단하다. [12:37 PM] rails는 full stack이 가능하다. 이말은 client도 rails에서 간단히 구현이 가능한데, 동일한 객체를 사용하는게 가능하다. 예를들어 model을 공유한다. [12:37 PM] 그래서 model을 직접 client와 server단에서 공유하고 같이 사용한다. 그것에 해당하는 api가 존재한다. [12:39 PM] broadcast_to, stream_for는 인자로 model을 받는다. 이렇게 받은 model을 client로 보낼수 있는것은 rails로 fullstack을 작성할때 사용하는 방식이다. 많은 rails cable를 사용하는 예제들 보통 chatting을 많이 예로 보여준다. [12:39 PM] chatting의 경우도 rails에서 server client를 모두 작성한다. 이때는 위와 같이 model을 그대로 보내는게 가능하다. [12:40 PM] 그런데 내 경우는 flutter라는 다른 external app이다. 이런경우는 model을 그대로 보내주는 stream_for나 broadcast_to를 사용하는게 아니다. 다른 함수를 사용해야 하고, data도 json형태로 보내야 한다. [12:41 PM] model을 json으로 보내는건 간단하다. [12:41 PM] 그리고 json을 broadcast하는 함수도 간단하다. 그냥 쓰면 된다. [12:42 PM] 여튼 점심먹고 와서 다시 해보자.
hoyoul — 12/05/2023 12:44 PM data를 json으로 보낸다는건 적절하진 않을수도 있다. key:value의 hash table? dictionary형태로 주고받는다. 뭐 똑같은 말이다. [12:45 PM] 1시까지 하고 밥먹으로 가자. [12:46 PM] 그러면 broadcast하지 않고, 개개인한테 보낼려면? id교환이 필요하다. 이전에 말했듯이 current_user를 사용할수도 있고 custom id를 사용할 수도 있다. [12:47 PM] 그러면 game방 사람들을 관리하는건? [12:47 PM] 당연히 list로 관리하던 data structure가 필요하고, 요청에 따라서 빈방찾고 빈방에 넣고 처리를 해야한다. [12:49 PM] 어차피 서버에서 처리해야하는것이다. [12:50 PM] 클라이언트에서 빈방을 관리할수 없기 때문이다. 관리할려면 클라이언트가 서버가 되야하니까..
rails는 정말 잘 만든 framework이다. 다시 한번 느낀다. [12:57 PM] 근데, 너무 마이너다..마이너중에 마이너가 되어버렸다. express도 건드려봤고, 장고도 건드려봤지만, 흉내낸거 같다. rails처럼 깔끔한 맛이 없다. [1:02 PM] 이제 동작은 거의 되니까, 순상님하고 물어보고 상의하고 서로 얘기하면서 구현을 같이 하는게 가능해 졌다.
hoyoul — 12/05/2023 2:06 PM 지금 내가 하고 싶은건 multi player에 대한 처리 확인이다. 2개의 client를 띄우고 rails에 접속해서 각각의 client에 대한 처리, broadcasting처리…등등..즉 여러명이 접속은 가능한지 그것부터 확인하고 싶다.
hoyoul — 12/05/2023 2:40 PM 좀…봐야할께 생겼다.

Figure 17: actioncable3
android와 ios를 두개를 실행시켜서 했을때 하나가 죽는 문제가 있었다. [2:56 PM] 해결한거 같다.
hoyoul — 12/05/2023 3:14 PM 이상하다. [3:14 PM] 새로운 gmail계정을 접속하니까…에러가 난다. [3:14 PM] 어디서 에러가 나는지 확인해야겠다. [3:15 PM] 새로운계정때문인지, 아니면 android emulator인지, 두개를 동시에 실행시켜서 그런지 확인이 필요하다. [3:17 PM] 지금까지 잘 진행했던 ios simulator에서 새로운 계정으로 테스트 해보자.
hoyoul — 12/05/2023 3:45 PM 동영상은 용량제한으로 올리지 못한다. 여튼 지금 android에서는 sign in error가 발생한다. 이게 google에러인지, rails와의 에러인지는 모르겠다. debugging해봐야 할듯하다. [3:48 PM] 디버깅 한다음 시료에서도 테스트를 해야한다. 시료 테스트를 너무 안했다. ios와 android도 해야하고, rails 실서버도 테스트해야 한다.
hoyoul — 12/05/2023 4:43 PM 우선 android emulator는 모르겠다. 시료에서 테스트해보자.
hoyoul — 12/05/2023 7:32 PM hub로 연결하면 device가 detect안되나? [7:32 PM] usb로 연결한 후 localhost접근이 될거라고 생각하는데, 그래야 rails server에 접속하니까… [7:33 PM] usb-c 케이블이 안보여서 usb a와 허브를 연결했는데 안보인다.
hoyoul — 12/05/2023 7:40 PM iphone은 인식이 잘되는데, android는 인식이 안된다. android studio, emacs 모두 마찬가지다. [7:40 PM] iphone하고 iphone simulator만 테스트해서 android를 신경쓰지 못했다. [7:40 PM] 왜 안될까.. [7:44 PM] https://developer.android.com/studio/run/device?hl=ko
https://hyunssssss.tistory.com/413
이게 더 그럴듯한데… [8:00 PM] 안드로이드 developer mode 설정 링크 [8:00 PM] https://devkim.tistory.com/6
이것대로 하니까 android studio에서 device가 뜬다.

Figure 18: android device1
안드로이드 폰도 동일한 에러다. [8:07 PM] emulator에서 발생했던것과 동일하다. [8:08 PM] emulator에서 debugging했을때는 google api가 제대로 동작을 안했었다. 결과값이 null이였다. [8:09 PM] 이말은 gcp 설정시 뭔가를 잘못 설정했거나, ios에선 google-serviceInfo.plist와 같은 것으로 설정을 해주었는데, android도 그런걸 해줬어야 하는 생각이 든다.
hoyoul — 12/05/2023 8:14 PM android도 googleserviceinfo.json이란게 있었다. [8:15 PM] 아…이것도 해줘야 하는거 같다. 근데 gcp는 내가 만든게 아니라서, 내가 로긴할수도 없어서 수정이나 발급을 받을수 없을꺼 같은데… [8:17 PM] 나는 그냥 자연씨 폴더에 있는 googleServiceInfo.plist를 copy해서 ios에 설치했었다. 그러면 자연씨가 android를 했었기 때문에 아무래도 android에 googleServiceInfo.json이 있을지 모른다. 찾아보자.
hoyoul — 12/05/2023 8:23 PM 엥…처리를 안해줬었네… [8:23 PM] 이상한데… [8:24 PM] 에러가 com.google.gms ~~인데 이건 json파일 문제가 맞는데.. [8:26 PM] 내가 지금 android에서 json얘길하는건 [8:26 PM] https://github.com/flutter/flutter/issues/70695
여기서 나오는 것처럼 PlatformException(sign_in_failed, com.google.android.gms.common.api.ApiException: 10: , null, null) [8:27 PM] 이런 에러가 나와서 보면, 질문한애는 google-service.json을 추가해줬는데도 에러가 난것이고..내경우는 아예 이 파일이 없다. [8:28 PM] 왜냐면 난 처리를 안했기 때문에… [8:28 PM] 자연씨도 ios 시료였나? [8:29 PM] 아닌데…여튼…오늘은 여기까지 하자. [8:29 PM] 낼 하자. 눈 아프다. December 6, 2023
12.6
test flight에서 android폰에서 구글인증을 확인했었다. 그러면 googleServiceInfo.json에 대한 처리가 되었다는 건데…내가 못찾은듯하다. [9:21 AM] 다시 찾아보자.
hoyoul — 12/06/2023 9:32 AM 찾았다. 처리해보자
hoyoul — 12/06/2023 10:28 AM 어..안된다.
hoyoul — 12/06/2023 10:43 AM 하나씩 해보자.

Figure 19: android device2
가장간단한건 gcp에 들어가서 확인하면 되는데…
hoyoul — 12/06/2023 11:45 AM https://support.google.com/cloud/answer/6158849?hl=ko#installedapplications&android&zippy=%2Cnative-applications%2Candroid
여기에 보면 android 에서 google oauth 인증을 받는법이 써져 있지만, 친절하지 않다. [11:46 AM] You need to specify your Android app’s package name and SHA1 fingerprint.
In the Package name field, enter your Android app’s package name. [11:47 AM] package이름과 SHA1 finger print를 기술할 필요가 있다. package name field에 android app의 package name을 넣으라고 되어 있다. [11:47 AM] 불친절하다. [11:47 AM] 어디에 specify하라는건가? [11:49 AM] 이 부분은 GCP에서 credentials을 만들때, androidManifest파일에서 package명을 얻을수 있다는 말에서 힌트를 얻을 수 있다. [11:50 AM] 왜냐? flutter package를 만든다고 하자. flutter create로 만들던, android studio에서 만들던, /android/app/src/main(release folder)이거나 debug(develop folder)에 있는 androidManifest파일에는 package명이 기술되어 있지 않다. [11:51 AM] default로 package명이 기술되어 있지 않다. [11:52 AM] package명은 graddle파일에 applicationID란 field에 정의되어 있다.
hoyoul — 12/06/2023 11:52 AM 그러면 왜? gcp에서 credentials를 만들때, android manifest에서 package명에서 가져오라는 말을 했을까? [11:54 AM] 이것은 개발자가 수동으로 입력해야하고, 그 입력을 하라는 얘기가 위의 you need specify….다. 즉 처음 만든 flutter package에 android의 mainfest파일에는 package명이 없다. graddle에서 applicationID값을 manifest.xml에 포함시키라는 것이다.

Figure 20: androidsetting1
그리고 sha1 fingerprint를 뽑아내야 하는데,
hoyoul — 12/06/2023 12:20 PM 음 우선 google play에 등록하는 과정을 생각해보자. [12:21 PM] — google play 등록 과정 — (edited) [12:21 PM] 나는 등록과정을 모른다. 하지만, 상상은 할 수 있다. [12:22 PM] 우선 appstore에 등록한다는 것은 여러가지 서식에 맞춰 뭔가를 작성할것이다. [12:22 PM] 그런데 가장 중요한건 app이다. app을 제출해야 한다. [12:22 PM] 그래야 google play에서 app을 받아서 배포할 것 아닌가? 상식적이지 않은가? [12:27 PM] 근데 그냥 app을 배포하면, 될까?
hoyoul — 12/06/2023 12:27 PM 아니다…이렇게 하니까…설명이 어렵다. [12:28 PM] sha1에대해서 얘기해보자. git을 보면 commit이란게 있다. commit은 동사로도 쓰이고 명사로도 쓰이는데, [12:28 PM] commit이 명사로 쓰일때 snapshot을 .git안에 object?에 저장한다. [12:29 PM] snapshot은 이전 commit과의 comparing결과로 보면 되는데, [12:29 PM] 여튼 snapshot을 저장할때 hash를 뽑아낸다. [12:30 PM] 그리고 그 hash의 첫번째 2글자와 나머지로 folder와 file을 만든다. [12:31 PM] hash는 유일한 값을 갖는다. 소수를 사용한 알고리즘을 사용한다. 뭐 이건 ds시간이나 algorithm을 공부하면 아는 내용이긴 한데, 여튼 [12:31 PM] sha1은 hash값이고 유일한 값이라고 보면된다. [12:32 PM] 마찬가지로 android app도 hash값을 뽑아낼수 있다. [12:33 PM] android app을 build하면 apk같은파일이 만들어지는데, 이게 인증서를 포함하고 있는데, [12:33 PM] 이것의 hash를 뽑아내면 어떤 일이 발생하냐… [12:34 PM] 내가 build한 app의 sha1이 342341…라고 할때,
만일 누군가가 app을 unpacking한 후 소스를 고치고, 악성코드를 심은후 다시 apk로 만들었다 하자. [12:36 PM] 그러면 그 apk의 sha1은 다른 값을 갖게 된다. [12:36 PM] 한글자만 달라도 sha1은 달라진다. [12:36 PM] 이것을 integrity라고 배운다. [12:36 PM] 한국말로는 모르겠다. [12:37 PM] 여튼 sha1을 playstore에서 요구를 한다. [12:37 PM] 즉 배포하는 입장에선 app의 integrity가 중요하다. 그래서 app을 등록한다면 아마도 sha1을 같이 입력할 것이다. [12:39 PM] 물론 지금은 app을 등록하는게 아니라, credentials을 생성하는데, sha1을 요구하기 때문에, 그것에 대해서 생각나는대로 써본것이다. [12:40 PM] 물론 앱을 등록하는데 요구하지 않을수도 있다. 그런데 credentials를 작성하는데도 sha1와 application id를 요구하는데, [12:40 PM] 배포하는데서 요구하지 않을 리는 없을것이다. [12:41 PM] 물론 다 뇌피셜이다. [12:42 PM] 그럼 ios에선 어떻게 했나? [12:42 PM] 아..기억이 안난다. credential을 처리할때 비슷하게 했을것이다.
hoyoul — 12/06/2023 12:42 PM 이건 내가 했으니까, 기록에 있을것이다.
hoyoul — 12/06/2023 12:50 PM 여튼 credential을 등록하는데 요구하는게 sha1이다. sha1을 하기전 apk를 만들고, 내가만든 인증서가 있다면 그걸 넣어준다. 그리고 sha1을 뽑아내야 한다. [12:55 PM] 인증서는 그냥 간단히 말하면 내가 rsa같은걸로 만들면 공개키와 비밀키가 만들어지는데, 인증서는 공개키를 포함하고 있다고 보면된다. [12:56 PM] 근데, 이게 아닌거 같다. 인증서를 동봉하는게 아니라, 개인서명을 한다고 한다. [12:56 PM] 여튼 지금까지 말한건 다 틀렸다. 여튼 중요한것은 sha1을 뽑아내야 한다는 것이다. 그래야 credentials를 얻을 수 있다. [12:57 PM] 그냥 뇌피셜로 상상한거다. 실제는 디지털 서명을 한다고 한다.
keytool -exportcert -alias androiddebugkey -keystore path-to-debug-or-production-keystore -list -v [1:27 PM] 이렇게해서 sha1을 뽑아낸다고 한다. [1:27 PM] 그런데 뭔말인지 모르겠다. [1:27 PM] keytool이 있나? [1:27 PM] keytool은 설치되어 있다.

Figure 21: sha1
인증서는 keystore에 저장되어 있다고 한다. [1:32 PM] -keystore path-to-debug-or-production-keystore 여기에 저장된 인증서를 뽑아서 alias붙여서 어떤내용인지 보여주겠다. 라는건데…. [1:32 PM] 이거로는 어떻게 해야할지 모르겠다. [1:33 PM] Note: For the debug.keystore, the password is android. For Android Studio, the debug keystore is typically located at ~/.android/debug.keystore. [1:33 PM] debug.keystore라는게 있기 하나보다. [1:33 PM] 찾아보자.
hoyoul — 12/06/2023 1:34 PM debug.keystore라는게 있다. [1:35 PM] keytool -exportcert -alias androiddebugkey -keystore ~/.android/debug.keystore -list -v [1:35 PM] 이렇게 하면되나? [1:36 PM] keytool -exportcert -alias androiddebugkey -keystore ~/.android/debug.keystore -list -v keytool error: java.lang.Exception: Only one command is allowed: both -exportcert and -list were specified. [1:36 PM] 에러가 난다. [1:39 PM] 그런데 바로 뒤에 다음 설명이 있다. [1:39 PM] keytool -list -v -keystore ~/.android/debug.keystore [1:39 PM] 이걸 실행해보자. [1:40 PM] 엥…비밀번호를 물어본다. [1:40 PM] 맥북 비밀번호가 아니다.
hoyoul — 12/06/2023 1:42 PM android를 입력해야 한다고 한다. [1:44 PM] sha1이 있다.

Figure 22: gcp setting1

Figure 23: gcp setting2
이렇게 googleservice.json이 만들어진다.
hoyoul — 12/06/2023 2:52 PM android/app/아래에 넣어둬야 한다고 한다. [2:53 PM] 그런데 ios의 경우, 나같은 경우는 googleservice-info.plist의 값을 info.plist에 넣어버렸다. 어차피 같은 plist라서… [2:54 PM] 근데, googleServices.json은 androidManifest에서 참조할텐데 [2:54 PM] 위치라도 알려주거나 load해서 사용해야 하는거 아닌가?
hoyoul — 12/06/2023 3:02 PM 음…안된다.
hoyoul — 12/06/2023 3:13 PM 조금만 쉬었다하자.
hoyoul — 12/06/2023 3:27 PM 다시 해보자.
/flutter ( 5517): Google Sign-In Error: PlatformException(sign_in_failed, com.google.android.gms.common.api.ApiException: 10: , null, null)
I/flutter ( 5517): ==========[ google sign in error ]=======
I/flutter ( 5517): Google Sign-In was canceled or failed.
E/flutter ( 5517): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: Exception: Google Sign-In was canceled or failed.
E/flutter ( 5517): #0 OauthService.relayGoogleToken (package:kholdem_v1/services/oauth_service.dart:43:7)
E/flutter ( 5517): <asynchronous suspension>
E/flutter ( 5517): #1 GameLoginController.executeOauth (package:kholdem_v1/controller/game_login_controller.dart:31:29)
E/flutter ( 5517): <asynchronous suspension>
E/flutter ( 5517): #2 GameLoginController.checkLogin (package:kholdem_v1/controller/game_login_controller.dart:68:7)
E/flutter ( 5517): <asynchronous suspension>
E/flutter ( 5517):
[3:31 PM]
똑같은 에러다. 즉 androidManifest에서 뭔가 해주는게 있을꺼 같은데…
hoyoul — 12/06/2023 3:40 PM https://velog.io/@zinkiki/FlutterAndroid-Unhandled-Exception-PlatformExceptionsigninfailed-com.google.android.gms.common.api.ApiException-10-null-null
동일한 에러인데, 이사람하고 나의 차이는 이 사람은 gcp설정을 자기가하고 자기가 앱을 테스트하는 경우고, 난 gcp설정을 하지 않았다. [3:43 PM] 그래서 이사람처럼 key를 생성해서 firebase에 등록해서 해결할 수가 없다. 물론 내가 gcp를 만들었다면, 이사람 처럼 sshkey를 gcp에 등록하고 다시 googleServices.json을 다운받아서 테스트했을수도 있다.
hoyoul — 12/06/2023 4:07 PM 자연시 코드를 비교해서 보는데, flutter가 업데이트가 많이 되서 구조가 완전 다르다. [4:07 PM] build.gradle을 눈여겨보는데, [4:07 PM] 구조는 다르지만, 독특한게 2가지가 있다. [4:07 PM] 하나는 signing하는것과, flavor설정이다. [4:08 PM] productFlavors { dev { dimension ‘build-type’ applicationIdSuffix “.dev” resValue “string”, “app_name”, “kholdem_dev” }
staging { dimension ‘build-type’ applicationIdSuffix “.staging” resValue “string”, “app_name”, “kholdem_staging” }
prod {
dimension 'build-type'
resValue "string", "app_name", "kholdem"
}
}
[4:08 PM] 즉 3단계로 나눠서 개발할려고 했다. 근데, 여기서 staging을 빼놓고는 중복이다. [4:09 PM] 왜냐면, 기본적으로 flutter에선 dev에 해당하는 flavor와 production에해당하는 flavor가 이미 있는걸로 알고 있다. [4:10 PM] 그래서 src에 보면 debug main profile이 있다. [4:10 PM] 이말은 dev이나 debug나 개발 process에선 거의 동의어라고 보면 된다. log level만 봐도 거의 같다. [4:11 PM] main은 flutter에서 말하는 production이다. [4:11 PM] release하는 버전이 사용하는 flavor라서 production과 같다. [4:12 PM] staging은 있을만 하긴 한데…즉 test단계인데…이것도 q&a부서가 따로 있는경우, 큰 회사에서 보통 사용하는거다.. [4:13 PM] 여튼 이건 내가 신경 안써도 될듯하다.
hoyoul — 12/06/2023 4:14 PM 그런데 하나가 더 신경쓰인다. [4:14 PM] defaultConfig { / TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html ). applicationId “io.kholdem.android” / You can update the following values to match your application needs. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration . minSdkVersion flutter.minSdkVersion targetSdkVersion flutter.targetSdkVersion versionCode flutterVersionCode.toInteger() versionName flutterVersionName } signingConfigs { debug { storeFile file(’../keystores/debug.keystore’) } release { keyAlias keystoreProperties[‘keyAlias’] keyPassword keystoreProperties[‘keyPassword’] storeFile keystoreProperties[‘storeFile’] ? file(keystoreProperties[‘storeFile’]) : null storePassword keystoreProperties[‘storePassword’] } } [4:14 PM] signingConfigs에 storefile이 있다는 거다. [4:14 PM] keystore다. [4:15 PM] 옛날에 후배들 막히면 다 이렇게 봐줬는데… [4:16 PM] 옛날 생각 난다. [4:16 PM] 어쨋든…
hoyoul — 12/06/2023 4:21 PM build시에 위에 정의된 keystore로 인증을 받는건, 위 keystore가 gcp에 등록된 keystore이기때문이다. 그런데 별도로 처리를 한건 자연씨가 가진 keystore는 gcp에 저장된게 아니다. 순상씨한테 받았던 본인이 접속해서 다시 등록을 했던…어떻게 했던…그 key store다. 그렇기 때문에 키를 별도로 저장하고 load해서 사용했을것이다. (edited) [4:24 PM] 자연씨가 했다고 확실히 말할 수 있는가? [4:24 PM] 비교를 해보니 그렇다. 즉 별도로 수정한거다. [4:24 PM] 원래 기본적인 값이 아니다.
hoyoul — 12/06/2023 4:32 PM 근데 저건 하드코딩이 아니라, local에 저장한거다.
hoyoul — 12/06/2023 4:41 PM 아..그리고 package name변경을 안했다. [4:42 PM] package=“io.kholdem.android” [4:42 PM] 이걸로 해줘야 한다. [4:43 PM] 왜냐면 gcp에는 등록한게 sha1과 package name이다. [4:44 PM] 즉 접속할때 2개의 값을 gcp에서 확인한다. [4:45 PM] package명이 androidManifest.xml에 기술이 안되어 있으면 등록된 app인지 알수가없다. [4:46 PM] googleSignIn()가 이값을 꺼내서 전달하기 때문이다. ios는 plist에서 값을 꺼내고, android의 경우 androidManifest.xml에서 꺼내서 전달한다. 물론 clientID도 꺼내서 전달한다. [4:48 PM] 인증서에 포함된 sha1값도 등록했기때문에 이것도 확인하는데, 확인하는 방법은 정확히 모르겠다.
hoyoul — 12/06/2023 4:49 PM
예상되는건, app이 실행되면, build.gradle에 정의된 keystore에서 sha1값을 꺼내서 가지고 있을듯하다. 그런데 왜 build.gradle이지? androidManifest.xml이 가지고 있으면 더 편하지 않나?
[4:50 PM]
build.gradle이 compile때 필요한 정보라면, androidmanifest는 runtime때 필요한 정보기 때문에,
[4:50 PM]
googleSignIn()가 보내기 더 좋지 않나?
[4:51 PM]
여튼, 자료가 없어서 거의 코드로만 해석하는데, 쉬운게 아니다.
[4:54 PM]
I/flutter ( 7025): Google Sign-In Error: PlatformException(sign_in_failed, com.google.android.gms.common.api.ApiException: 8: , null, null)
I/flutter ( 7025): ==========[ google sign in error ]=======
I/flutter ( 7025): Google Sign-In was canceled or failed.
E/flutter ( 7025): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: Exception: Google Sign-In was canceled or failed.
E/flutter ( 7025): #0 OauthService.relayGoogleToken (package:kholdem_v1/services/oauth_service.dart:43:7)
E/flutter ( 7025): <asynchronous suspension>
E/flutter ( 7025): #1 GameLoginController.executeOauth (package:kholdem_v1/controller/game_login_controller.dart:31:29)
E/flutter ( 7025): <asynchronous suspension>
E/flutter ( 7025): #2 GameLoginController.checkLogin (package:kholdem_v1/controller/game_login_controller.dart:68:7)
E/flutter ( 7025): <asynchronous suspension>
[4:54 PM]
api exception이 10에서 8로 변했다.
[4:56 PM]
이건 sha1처리 문제다. 즉 sha1이 다르다는거다. 이문제는 순상님이 아마 3-4일 후에 업무하게 되면 그때 물어보거나 요청하면 된다.
hoyoul — 12/06/2023 4:57 PM 여튼 지금 처리해야할것은 [4:57 PM] page이동도 해야한다. [4:57 PM] 즉 gameroom에 들어왔다면, 즉 join이 성공적으로 되었다면, [4:59 PM] 게임방으로 이동하는거 지금은 그냥 비동기처리가 안되어 있다. [4:59 PM] 비동기가 까다로운건, 함수의 호출 flow를 다 점검해야한다는 점이다.
hoyoul — 12/06/2023 8:36 PM 잠깐 나갔다 오자. 상황봐서 이어서 하던지, 낼 하던지 하자.
hoyoul — 12/06/2023 9:39 PM 우선 debug파일을 받았다. [9:40 PM] 이것을 자연님 했듯이 똑같이 해보고 되는지 안되는지만 확인하자. [9:41 PM] 자연님은 폴더를 만들었다. storeFile file(’../keystores/debug.keystore’) [9:44 PM] 이게 기술된 파일이 flutter프로젝트/android/app/build.gradle이니까…android/keystores 폴더가 있고, 거기에 저이름으로 저장하면 된다. [9:46 PM] 근데 이게 key파일이라서 repo에서 제외됐기 때문에 [9:46 PM] 내가 clone한 working directory에는 보이지 않는다. [9:46 PM] 그래서 그냥 만들고 붙여넣어야 한다.
hoyoul — 12/06/2023 9:48 PM 이렇게 하고, 똑같이 build.graddle을 자연씨거하고 같게한다. [9:48 PM] 이게 형식이 달라져서 잘 보고 해야한다. [9:54 PM] 많이 다르다. 우선, signing관련한것만 추가해서 해보고 안되면, 전체를 그냥 복사해서 해보고 안되면, gradle파일을 분석하고 처리하자.
hoyoul — 12/06/2023 9:56 PM rails 서버를 띄우고, [9:56 PM] flutter clean을 하고 [9:56 PM] android emul띄우고 [9:56 PM] flutter run으로 실행해보자. [9:58 PM] FAILURE: Build failed with an exception.
Where:
Build file ‘/Users/hoyoul/Development/Projects/kholdem-front/android/app/build.gradle’ line: 58
What went wrong:
A problem occurred evaluating project ‘:app’. > Could not get unknown property ‘keystoreProperties’ for SigningConfig$AgpDecorated_Decorated{name=release, storeFile=null, storePassword=null, keyAlias=null, keyPassword=null, storeType=pkcs12, v1SigningEnabled=true, v2SigningEnabled=true, enableV1Signing=null, enableV2Signing=null, enableV3Signing=null, enableV4Signing=null} of type com.android.build.gradle.internal.dsl.SigningConfig$AgpDecorated.
Try:
> Run with –stacktrace option to get the stack trace. > Run with –info or –debug option to get more log output. > Run with –scan to get full insights.
Get more help at https://help.gradle.org
BUILD FAILED in 656ms Running Gradle task ‘assembleDebug’… 1,145ms Exception: Gradle task assembleDebug failed with exit code 1
Process Flutter exited abnormally with code 1 [9:58 PM] 에러가 났다. [9:58 PM] 지금 수정한 build.gradle에러다. [10:02 PM] 이것은 대충은 뭔말인지 알긴 알겠는데… [10:02 PM] 우선 동작만 시켜보자. [10:02 PM] 저건 release와 관련된거라서…우선…그부분을 다 comment하고 테스트해보자. [10:03 PM] emacs에서 gradle을 사용해본적이 없어서 gradle모드가 없다.
hoyoul — 12/06/2023 10:03 PM gradle format을 읽을수 있는 mode를 설치하자. [10:07 PM] FAILURE: Build failed with an exception.
What went wrong:
Execution failed for task ‘:app:validateSigningDebug’. > Keystore file ‘/Users/hoyoul/Development/Projects/kholdem-front/android/keystores/debug.keystore’ not found for signing config ‘debug’.
Try:
> Run with –stacktrace option to get the stack trace. > Run with –info or –debug option to get more log output. > Run with –scan to get full insights.
Get more help at https://help.gradle.org
[10:07 PM] 다른에러다. 이건 경로 에러다. 즉 오타다.
hoyoul — 12/06/2023 10:10 PM adb: failed to install /Users/hoyoul/Development/Projects/kholdem-front/build/app/outputs/flutter-apk/app-debug.apk: Failure [INSTALL_FAILED_UPDATE_INCOMPATIBLE: Existing package com.example.kholdem_v1 signatures do not match newer version; ignoring!] Uninstalling old version… Installing build/app/outputs/flutter-apk/app-debug.apk… 2,090ms Syncing files to device sdk gphone64 arm64… 74ms [10:10 PM] 에러가 났는데, 좀 다르다. [10:11 PM] 이전에는 build에서 멈췄다면, 이번엔 실행까지 되었다. 근데. 보면 package name이 어딘가 잘못된걸 볼수 있다. [10:11 PM] package name이 io로 시작되어야 한다. [10:11 PM] com.example이라고 되어 있는거 다 고쳐야 한다. [10:12 PM] 이건 좀 시간 걸릴듯 하다. 낼 하자. December 7, 2023
hoyoul — 12/07/2023 12:30 AM android { // namespace “com.example.kholdem_v1” namespace “io.kholdem.android” compileSdkVersion flutter.compileSdkVersion ndkVersion flutter.ndkVersion [12:30 AM] build.gradle에서 namespace가 com.example.kholdem_v1으로 되어 있어서 고쳤다. [12:30 AM] 다시 실행하니까 에러가 났다. [12:31 AM] ✓ Built build/app/outputs/flutter-apk/app-debug.apk. E/AndroidRuntime(10208): FATAL EXCEPTION: main E/AndroidRuntime(10208): Process: com.example.kholdem_v1, PID: 10208 E/AndroidRuntime(10208): java.lang.RuntimeException: Unable to instantiate activity ComponentInfo{com.example.kholdem_v1/io.kholdem.android.MainActivity}: java.lang.ClassNotFoundException: Didn’t find class “io.kholdem.android.MainActivity” on path: DexPathList[[zip file “/data/app/~~qDb3WleQ82VJMLHG3qNf_Q==/com.example.kholdem_v1-vxUWiOiXWYoV-tNeziHYwg==/base.apk”],nativeLibraryDirectories=[/data [12:33 AM] MainActivity가 entry point인데, 이게 호출 경로가 좀 이상하다. class를 찾지 못한다. 이유는 io.kholdem.android.MainActivity에 com.exmple.kholdem.v1/이란 폴더?가 있다. [12:33 AM] MainActivity를 우선 찾아서 까보자. [12:34 AM] package com.example.kholdem_v1
import io.flutter.embedding.android.FlutterActivity
class MainActivity: FlutterActivity() { } [12:34 AM] android를 사용한다는건 껍데기만 사용하는데, 그 껍데기 entry point가 MainActivity다. [12:35 AM] 내용물은 다 flutter/lib에 있는걸 사용할것이다. 그런데 package명이 잘 못됐다. [12:35 AM] 바꿔보자.
hoyoul — 12/07/2023 12:39 AM
I/flutter (10323): Google Sign-In Error: PlatformException(sign_in_failed, com.google.android.gms.common.api.ApiException: 10: , null, null)
I/flutter (10323): ==========[ google sign in error ]=======
I/flutter (10323): Google Sign-In was canceled or failed.
E/flutter (10323): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: Exception: Google Sign-In was canceled or failed.
E/flutter (10323): #0 OauthService.relayGoogleToken (package:kholdem_v1/services/oauth_service.dart:43:7)
E/flutter (10323): <asynchronous suspension>
E/flutter (10323): #1 GameLoginController.executeOauth (package:kholdem_v1/controller/game_login_controller.dart:31:29)
E/flutter (10323): <asynchronous suspension>
E/flutter (10323): #2 GameLoginController.checkLogin (package:kholdem_v1/controller/game_login_controller.dart:68:7)
E/flutter (10323): <asynchronous suspension>
[12:39 AM]
다시 똑같은 에러다.

Figure 24: gcp error1
안되겠다. 내가 graddle과 androidManifest.xml과 동작 방식을 몰라서 헤매고 있는듯 하다. [12:52 AM] 낼 봐야겠다.
</img/oauth/gcperror.mp4>
디버깅으로 google sigin in만 가져오는건 처리했음. [2:16 AM] 근데 문제가 있어보임. gcp설정문제도 그렇고 port변경문제도 있음. [2:17 AM] google cloud platform설정이 뭔가 이상함. 내가 예전에 만들어서 테스트했을때와 다름. 한번 봐야함.
</img/oauth/port_change.mp4>
flutter에서 localhost로 연결하면 android기기의 경우 localhost가 emulaotor로 잡힌다.그래서 port가 변한다. local rails server에 접속하기 위해서 localhost를 사용하면 안된다.
동일한 소스에서 안되는 문제 해결중.
hoyoul — 12/07/2023 12:24 PM 어느정도 실마리는 얻었다. [12:25 PM] 테스트는 밥먹고 와서 하자.
hoyoul — 12/07/2023 1:58 PM 음..잘 안된다. [1:58 PM] 시료로 해볼까? [1:59 PM] 하나만 더 테스트하고 시료로 해보자.
hoyoul — 12/07/2023 2:26 PM 해결한거 같다. [2:27 PM] gcp문제인지 모르는건, 우선 놔두고… [2:27 PM] 시료 테스트를 해봐야겠다. 근데…시료는 의미가 없긴하다. local test라서… [2:28 PM] 10분만 쉬자.
12.8
지금 문제점이 있다. [8:02 AM] rails로 연결할때, 500 에러가 난다. [8:03 AM] 디버깅을 했었다. idToken이 null이다. [8:03 AM] rails는 id token을 받아서 token의 유효성을 google에 물어본다. [8:04 AM] flutter로부터 받은 idToken이 null이기 때문에 google에서 응답을 못받아서 500에러를 return한다. [8:05 AM] 내가 처음에 이해를 못했던게 2가지가 있다. [8:05 AM] 첫째, 왜 token을 hard coding하나? [8:06 AM] 왜 순상님이 token을 알려주고 쓰나, 왜냐면 flutter에서 google에서 token을 받는데, token을 hard coding할 이유가 전혀없었기 때문이다. [8:06 AM] 그 이유는 idToken이 null이라서 에러가 난다는 사실을 알고 있었다는 얘기다. [8:07 AM] 두번째, id token을 까서 인증을 하지 않는다는 거였다. [8:07 AM] 물론 이것은 google에 물어봐서 처리가 되니까…pass해도 되긴하다. [8:08 AM] 하지만, google에 물어보는건 횟수제한이 있다.
hoyoul — 12/08/2023 8:08 AM 여튼 어제부터 지금까지, 이 문제를 해결할려고 했는데..잘 안됐다.
hoyoul — 12/08/2023 11:22 AM consent screen에서 testing user에 관해서 [11:23 AM] testing user 설정을 하면, 동의화면과 login화면이 해당 testing user들에게만 보여줄거라 생각했다. (edited) [11:23 AM] 그래서 나는 testing user설정을 하지 않았다. 귀찮기도 하고, 그냥 모든 사람들이 test할수 있는게 편하니까… (edited) [11:24 AM] 이렇게 하고 scope를 email,profile, openId 3개를 주었다. [11:24 AM] 이런경우 모두 이상없이 tokenID를 포함한 3개의 token을 받아오고 consent screen이 보여지는걸 확인했다. [11:25 AM] 그런데 testing user가 의미하는건 내가 생각한것과 반대였다. [11:25 AM] testing user에 등록된 사람들은 consent screen도 보여지지 않고 login화면도 보여지지 않는다. 즉 oauth과정이 생략된다. [11:26 AM] 왜냐면 test할 유저들은 oauth과정을 test할 사람이 아니고, 개발 test하는데있어서 oauth과정을 pass하고 test할 사람들이기 때문이다. [11:26 AM] 즉 oauth처리 과정을 pass할 사람들을 testing user에 기록하는것이다. [11:27 AM] 내가 이해를 못했던게, 처음 설정할때, internal과 external을 선택하는데, internal로 선택하던 external로 선택하던 모두 consent screen과 로긴 화면을 받는데 문제가 없어서… [11:28 AM] 이게 의미가 있나? 했는데..이게 testing user에서 구체화하는거 같다. [11:28 AM] 여튼 결론은 testing user에 등록하지 않으면 oauth처리를 거치게 된다. (edited)
hoyoul — 12/08/2023 11:31 AM 그리고 아까 port에러는 내가 port번호 오타가 있었다. 지금 확인했는데, 다시 test해야 한다. [11:35 AM]
- port test : android,ios 정상적으로 되는지 확인 시료도 확인
[11:35 AM]
- name가져오는거 되는지 확인: local, production 확인
[11:36 AM]
- websocket stream for를 subscribe에서 되는지 확인
[11:36 AM]
- join버튼누르면 게임화면 이동
[11:36 AM]
- game에서 화투패 애니메이션
[11:37 AM]
- 피망에서 베팅하는 방법 배우기
[11:37 AM]
- join이 되니까 다른 것도 test해서 ui변경까지.
[11:37 AM] 대략 생각나는것을 써놨다. [11:37 AM] 우선 주말까지 이 순서대로 해보자.
hoyoul — 12/08/2023 11:39 AM
- port test : android,ios 정상적으로 되는지 확인 시료도 확인
hoyoul — 12/08/2023 11:58 AM
Started POST “/auth/google_oauth2_with_id_token” for 127.0.0.1 at 2023-12-08 11:56:35 +0900
ActiveRecord::SchemaMigration Pluck (1.6ms) SELECT “schema_migrations”.“version” FROM “schema_migrations” ORDER BY “schema_migrations”.“version” ASC
Processing by OmniauthCallbacksController#google_oauth2_with_id_token as HTML
Parameters: {“omniauth.auth”>{"credentials"=>{"token"=>"[FILTERED]"}, "extra"=>{"id_token"=>"[FILTERED]"}, "server_auth_code"=>nil}, "omniauth_callback"=>{"omniauth.auth"=>{"credentials"=>{"token"=>"[FILTERED]"}, "extra"=>{"id_token"=>"[FILTERED]"}, "server_auth_code"=>nil}}} response: #<Faraday::Response:0x0000000106260230 @on_complete_callbacks[], @env=#<Faraday::Env @method=:get @url=#<URI::HTTPS https://oauth2.googleapis.com/tokeninfo?id_token=eyJhbGciOiJSUzI1NiIsImtpZCI6ImU0YWRmYjQzNmI5ZTE5N2UyZTExMDZhZjJjODQyMjg0ZTQ5ODZhZmYiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJhenAiOiI5NjA4NjkzMzE5MTUtaWo3ZTB1aGI2YWk5Z2w0czA2azZucGIzaXJyOG90dnAuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJhdWQiOiI5NjA4NjkzMzE5MTUtaWo3ZTB1aGI2YWk5Z2w0czA2azZucGIzaXJyOG90dnAuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJzdWIiOiIxMDczOTQxMDkwMjAzMzY0MzAyNzYiLCJlbWFpbCI6ImhveW91bC5wYXJrQGdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJhdF9oYXNoIjoiWXlBU1VvOHZTUUhwR04wZDZITEl5USIsIm5vbmNlIjoiczB4b1lkNWJJSGhjVmMteUhheWQ4QXpfTnQ4Y09RSnFQV0ZCTXJlUGNINCIsIm5hbWUiOiJIb3lvdWwgUGFyayIsInBpY3R1cmUiOiJodHRwczovL2xoMy5nb29nbGV1c2VyY29udGVudC5jb20vYS9BQ2c4b2NJT3NScWhaakRTOHpWcFhSdzgzZkV6YWFwcW5ReWIyX25wMlZUN0tEOHprN0E9czk2LWMiLCJnaXZlbl9uYW1lIjoiSG95b3VsIiwiZmFtaWx5X25hbWUiOiJQYXJrIiwibG9jYWxlIjoiZW4iLCJpYXQiOjE3MDIwMDQxODEsImV4cCI6MTcwMjAwNzc4MX0.n9v360j46b4QqIlY_lM7eLn5_cqNwkqrdftpYfPmTF3BGLnPrMysLITH6nXK6xjVrn_LRMBWJq37RETAf1RtOY_szg6a6KdGF2odwOUGE6MdefZ2IJ2EFt8Rzo3tvC9mn_hOabBWvTVmsSe1_P8ughFx-16cSgr2lM1_WTZxuG_miJv4Sv9Gr8_48MSpmze35G9p-p8bmnB3SlzK16lIYj4jFDb9bBnlJ6-Qs7tty2XDNRe9L_fRKadwDfHSgfc0_Deo6a1yBV7Tw-CtTaIvtSt4KUusztMLHKZD9Kr8_MtgRkQBEOQBdnoW68wS4shEOV3VA4VzOyVpYA67Md4mrQ
> @request=#<Faraday::RequestOptions (empty)> @request_headers={“User-Agent”>"Faraday v2.7.10"} @ssl=#<Faraday::SSLOptions verify=true> @response=#<Faraday::Response:0x0000000106260230 ...> @response_headers={"cache-control"=>"no-cache, no-store, max-age=0, must-revalidate", "pragma"=>"no-cache", "date"=>"Fri, 08 Dec 2023 02:56:35 GMT", "expires"=>"Mon, 01 Jan 1990 00:00:00 GMT", "content-type"=>"application/json; charset=UTF-8", "vary"=>"Origin, X-Origin, Referer", "content-encoding"=>"gzip", "server"=>"ESF", "x-xss-protection"=>"0", "x-frame-options"=>"SAMEORIGIN", "x-content-type-options"=>"nosniff", "alt-svc"=>"h3\":443\”; ma=2592000,h3-29=\":443\"; ma=2592000", “transfer-encoding”>"chunked"} @status=200 @reason_phrase“OK” @response_body="{\n \“iss\”: \"https://accounts.google.com\",\n
\“azp\”: \“960869331915-ij7e0uhb6ai9gl4s06k6npb3irr8otvp.apps.googleusercontent.com\”,\n \“aud\”: \“960869331915-ij7e0uhb6ai9gl4s06k6npb3irr8otvp.apps.googleusercontent.com\”,\n \“sub\”: \“107394109020336430276\”,\n \“email\”: \“hoyoul.park@gmail.com
\”,\n \“email_verified\”: \“true\”,\n \“at_hash\”: \“YyASUo8vSQHpGN0d6HLIyQ\”,\n \“nonce\”: \“s0xoYd5bIHhcVc-yHayd8Az_Nt8cOQJqPWFBMrePcH4\”,\n \“name\”: \“Hoyoul Park\”,\n \“picture\”: \"https://lh3.googleusercontent.com/a/ACg8ocIOsRqhZjDS8zVpXRw83fEzaapqnQyb2_np2VT7KD8zk7A=s96-c%5C%22,%5Cn
\“given_name\”: \“Hoyoul\”,\n \“family_name\”: \“Park\”,\n \“locale\”: \“en\”,\n \“iat\”: \“1702004181\”,\n \“exp\”: \“1702007781\”,\n \“alg\”: \“RS256\”,\n \“kid\”: \“e4adfb436b9e197e2e1106af2c842284e4986aff\”,\n \“typ\”: \“JWT\"\n}\n”>>
TRANSACTION (0.3ms) BEGIN
Expand
message.txt
5 KB
[11:59 AM]
rails에서 다음과 같은 에러가 난다.
[11:59 AM]
500에러는 token과 관련한거 일텐데…
[12:00 PM]
ios에서 simulator에서 id_token은 확인했다.
[12:00 PM]
바뀐건 2개가 있다. 하나는 port번호..두번째는 db에서 record지운거.
[12:01 PM]
근데 에러는 500이다.
[12:01 PM]
port는 문제가 없어보이긴 하다. 왜냐면 port가 문제가 있으면 socket에러가 나고 port번호가 찍힌다.
[12:01 PM]
그럼 token문제일수도?
[12:02 PM]
token을 찍어봤을때…문제가 없다.
[12:02 PM]
db는 정확하지 않지만, rails에서는 db table이나 db를 직접 변경하지 않는다. 에러나니까…그런데 record지우는건 상관없는거 같은데…
[12:05 PM]
다시 token이 제대로 전달되는지 확인해보자.
[12:05 PM]
500은 토큰문제일 확률이 크다.
hoyoul — 12/08/2023 12:06 PM 다시 bp걸고 확인하자.
hoyoul — 12/08/2023 12:24 PM 우선은 점심먹고 다시 진행하자.
</img/oauth/port_change.mp4>
ios는 우선 port변경 잘된다. [1:37 PM] android 해보자. [1:40 PM] android port에러는 또 난다. 디버깅해보자.
</img/oauth/tokenproblem1.mp4>
500에러난다. 즉 token을 못가져온다. [2:05 PM] 이건 내가 예상했던 testing user에서 포함안되서 이런결과가 나왔다고 한 추측이 모두 틀렸음을 의미한다. [2:06 PM] 그럼 아까 consent화면이 뜬건 왜 뜬걸까? [2:06 PM] 뭘 어떻게 했길래 떴지? [2:06 PM] emulator문제? cache…이건 이전에도 안했었기 때문에.. [2:07 PM] 물론 cache야…지우고 하면 된다. [2:07 PM] 그러면 시료로도 해보자.
cache는 ~/.android/emulator명/cache관련 파일을 지우면된다. [2:12 PM] 시료부터 하고, 그다음 cache해보고…. [2:12 PM] 그러면 자연님 소스에서 차이는 flavor니까…그거 맞춰서 다시 해보자. [2:17 PM] 시료가 안보인다. 허브에 연결해서 그런가?
wireless debugging을 했더니 smA908이 떴다. [2:27 PM] 그리고 app이 실행되면서 consent화면이 떴다. [2:28 PM] rails에는 별다른 로그가 안보이는거 봐서는…좀 이상하다. [2:28 PM] 500에러라도 보여야 정상인데… [2:30 PM] mpl@4941f9fMainActivity : ViewPostIme pointer 0 I/ViewRootImpl@4941f9fMainActivity : ViewPostIme pointer 1 I/DecorView(22855): [INFO] isPopOver=false, config=true I/DecorView(22855): updateCaptionType >> DecorView@80769fd[], isFloating=false, isApplication=true, hasWindowControllerCallback=true, hasWindowDecorCaption=false D/DecorView(22855): setCaptionType = 0, this = DecorView@80769fd[] I/DecorView(22855): getCurrentDensityDpi: from real metrics. densityDpi=420 msg=resources_loaded Expand message.txt 5 KB [2:30 PM] android studio에서 뽑은 로그다. [2:31 PM] 별다른건 없어보인다. [2:32 PM] android기기에서는 처음에는 signing화면이 보이지만, 두번째에선 보이지 않는다. [2:32 PM] 순상님이 말했듯 캐시 문제인가? [2:32 PM] 확인해보자.
hoyoul — 12/08/2023 2:34 PM signIn을 하고 sign out을 하지 않은것도 영향이 있는가? [2:35 PM] 우선 cache를 지우고 테스트 해보자. [2:35 PM] 10분만 쉬자..눈아프다.
hoyoul — 12/08/2023 2:42 PM 안된다.. [2:42 PM] 컴퓨터를 리스타트하고 다시 해보자. [2:48 PM] 또같다. sign in 시에 null이 나온다. [2:48 PM] 그러면 signout처리하고 다시 signIn만 해보자. 안되면 넘어가자.

Figure 29: consent logout
logout버튼을 만들었다. [3:08 PM] 그리고 android에서 테스트 했다. 우선 다시 consent화면이 나왔다. [3:08 PM] 이게 맞는지는 모르겠다. [3:08 PM] 우선 token오는거 확인하자. [3:08 PM] 이제 맞다면, ios와 android는 차이가 많다. [3:09 PM] 우선 ios는 logout을 하지 않아도 app이 종료되면 자동으로 signout처리를 해준다는 것이다. [3:10 PM] 즉 google에 logout메시지를 보내기 때문이다. [3:10 PM] 반면 android는 logout처리를 하지않으면 google에 logout메시지를 보내지 않기때문에 [3:10 PM] logout이 안된 상태라는 얘기다. [3:11 PM] token확인해보고 제대로 되면 시료에서도 확인해보자.
</img/oauth/consent_logout.mp4>
consent화면은 계속 뜬다. [3:16 PM] 그런데 token은 가져오지 않는다. [3:16 PM] 왜 그럴까? 다음을 보자. consent화면은 logout을 해줘야 뜬다. logout을 해주지 않으면 다음 접속때 consent화면이 뜨지 않는 문제가 있다.
</img/oauth/consent-logout2.mp4>
확실히 logout처리로 consent screen을 가져오지만, idToken은 가져오지 않는다.
hoyoul — 12/08/2023 3:25 PM flutter에선 google에서 제공하는 package, googleSignIn의 signIn()를 호출할 뿐이다. 이 함수가 하는건, clientID로 google과 연결하고 거기서 작성된 consent screen보여주고, login을 인증하고 부여된 권한에 따른 token 3개를 전달한다. 즉 google에서 처리하는것이다. [3:28 PM] 한가지 생각할께 있긴 하다. [3:30 PM] 내가 요청할때 email만 요청해서 그럴까? 근데 이건 말이 안되는게 [3:31 PM] 지금까지 받아왔기 때문에 이걸 바꾼다고 받아온다면 그것도 이상한거다. [3:32 PM] 이전에 안드로이드는 그럼 문제 없이 잘되었다면, 차이는 하나다. [3:32 PM] 다른 코드는 하나밖에 없다. build.gradle에서 flavor인데…
hoyoul — 12/08/2023 3:32 PM 그거말고는 차이가 없다. [3:33 PM] 이전에 잘되었는데 지금 안될일은 없기 때문이다.
hoyoul — 12/08/2023 3:45 PM 내가 예전에 test했던 gcp에 hash값하고 app이름만 변경해서 해보자.
hoyoul — 12/08/2023 3:52 PM 나는 google sign in이 어떻게 돌아가는지 안다고 생각한다. 그에 대한 확신이 있다. [3:53 PM] 그러면 테스트프로그램으로 증명할수 있다고 생각한다. [3:54 PM] 오늘 밤을 새서라도 확인할 것이다.
hoyoul — 12/08/2023 4:07 PM 지금 소스의 sha1을 동일하게 하는건 안되고, 새롭게 clone하고 package명 hash코드를 바꿔서 할수도 있고, [4:08 PM] 간단하게 login app을 만들어서 할수도 있다. [4:08 PM] login app만들어서 하자. [4:08 PM] 어차피 signedIn package만 깔면 된다.

Figure 32: test1
간단하게 2개의 button이 있는 앱을 만들었다. [4:20 PM] 어차피 코드는 debugging으로 다 체크할것이기 때문에 [4:20 PM] 등록하고 sign in()로 확인해보자. [4:25 PM] 근데 예제를 보니, 좀 이상한게 있다. [4:25 PM] _googleSignIn.onCurrentUserChanged .listen((GoogleSignInAccount? account) async { / In mobile, being authenticated means being authorized… bool isAuthorized = account != null; / However, in the web… if (kIsWeb && account != null) { isAuthorized = await _googleSignIn.canAccessScopes(scopes); }
setState(() { _currentUser = account; _isAuthorized = isAuthorized; });
/ Now that we know that the user can access the required scopes, the app / can call the REST API. if (isAuthorized) { unawaited(_handleGetContact(account!)); }
hoyoul — 12/08/2023 4:27 PM 이건 api와 관련된거라서… [4:27 PM] 나는 이렇게 처리할 필요는 없어보이긴 하다. [4:28 PM] 사용자의 변경시에 처리과정이 나와 있어서 눈길이 갔는데… [4:32 PM] 여튼 지금 간단히 sign in , out를 적용했다. [4:32 PM] 이제 gcp를 만들어서 테스트 해보자. [4:33 PM] oauth screen을 만들었다. [4:33 PM] 그다음 credentials를 만들자. [4:34 PM] android만 할꺼다.
hoyoul — 12/08/2023 4:35 PM sha1을 뽑아야 하는데… [4:35 PM] 나는 원래 keystore를 만들지 않았다. [4:35 PM] 자동으로 android studio에서 만드는지는 모르겠다. [4:35 PM] 그냥 keytool로 만들자. [4:39 PM] keytool -genkey -v -keystore debug.keystore -storepass android -alias androiddebugkey -keypass android -keyalg RSA -keysize 2048 -validity 10000 [4:40 PM] 이렇게 만들면 된다는데…맞나? keysotre 는 debug.keystore고 password는 android다. [4:40 PM] 그리고 rsa나 시간 설정은 이해가 되는데.. [4:40 PM] 뭐 해보자.
hoyoul — 12/08/2023 4:42 PM 여튼 만들어 졌다. [4:42 PM] 여기서 sha1을 뽑아야 한다. [4:42 PM] keytool -keystore path-to-debug-or-production-keystore -list -v [4:43 PM] 뭐이런식으로 하면 된다고 한다. path만 바꿔서 해보자. [4:44 PM] sha1을 뽑았다. [4:44 PM] 이렇게 하면 다 만들었다. [4:46 PM] 이제 google service.json을 넣고 test하면 된다. [4:46 PM] 해보자. [4:48 PM] 혹시나 모르니 emul,simul, emacs 모두 다시 시작하자.
hoyoul — 12/08/2023 4:51 PM consent screen은 나왔는데, [4:52 PM] 변수를 받아서 처리하는 부분을 넣어야 겠다. 그냥 호출만 해서 넘어가버렸다. [4:54 PM] 아…그리고 이걸 테스트해야겠다. [4:54 PM] GoogleSignIn({SignInOption signInOption = SignInOption.standard, List<String> scopes = const <String>[], String? hostedDomain, String? clientId, String? serverClientId, bool forceCodeForRefreshToken = false}) [4:55 PM] 궁금한건 첫째 standard가 뭐고 standard말고 다른것도 있는가? [4:55 PM] clientId와 serverClientId차이는 무엇인가? [4:56 PM] forceCodeForRefreshToken이 default값이 false가 맞는가? [4:56 PM] 좀 쉬었다가, 수영장을 가야 하는데.. [4:56 PM] 어제도 한숨 못잤다..그냥 지금 한숨 자자.
hoyoul — 12/08/2023 5:41 PM com.google.android.gms.common.api.ApiException: 12500 [5:41 PM] 에러인데, finger print관련이라고 한다.
hoyoul — 12/08/2023 6:00 PM ios하고 많이 다르다. [6:00 PM] key만드는게 잘못된것인가? 아니면 설정이 또 필요한가?
hoyoul — 12/08/2023 6:08 PM 설정이 필요하다. 왜냐면 [6:08 PM] stack overflow보면, firebase이긴 하지만, 설정을 한다. [6:08 PM] 그리고 ios에서도 다 설정을 했다. [6:09 PM] 그 설정을 찾아서 하면 된다. [6:09 PM] build.gradle과 androidManifest.xml밖에 없다. [6:14 PM] 근데, 분명히 자연씨는 android는 성공을 했다. 그래서 뭔가가 build.gradle과 androidManifest.xml이 다른게 있을것이다.

Figure 33: graddle1
간단한 예로, android폴더의 설정파일을 비교하는데, 많이 다르다. 이게 flutter가 업그레이드되서 바뀐건지 아니면 자연씨가 다 수정해서 바뀐건지는 모르겠다.
hoyoul — 12/08/2023 6:31 PM 될지 안될지 모르겠지만, [6:31 PM] <meta-data android:name=“io.flutter.embedding.android.NormalTheme” android:resource="@style/NormalTheme" <!– added by hoyoul –> android:name=“com.google.android.gms.auth.api.credentials.CLIENT_ID” android:value=“1035430075667-f3appetqc887ht5e9s469gbnpj74d7fn.apps.googleusercontent.com” /> /> <intent-filter> <action android:name=“android.intent.action.MAIN”/> <category android:name=“android.intent.category.LAUNCHER”/> [6:32 PM] clientID를 추가해봤다. [6:35 PM] parsing error가 난다.
hoyoul — 12/08/2023 6:47 PM 좀 쉬었다하자. 눈아프다.
hoyoul — 12/08/2023 6:56 PM 분명한건 sha1문제이니까, keystore만드는 방식에 문제가 있을 수 있다. [7:02 PM] 엥… [7:02 PM] .android/debug.keystore라는게 원래 있네.. [7:02 PM] 새롭게 만드는게 아니라..
hoyoul — 12/08/2023 7:09 PM ok 성공했다. [7:10 PM] 아…기본적으로 android는 id_token을 못가져온다. signIn()만 호출해선 안될꺼 같고, 옵션을 변경해서 테스트해봐야 한다.
hoyoul — 12/08/2023 7:45 PM 시료 테스트를 해보자. [7:46 PM] 시료도 못가져온다.
hoyoul — 12/08/2023 8:05 PM https://stackoverflow.com/questions/63891637/getting-idtoken-null-when-using-google-sign-in-in-flutter
내 문제와 가장 비슷한 처리인데, 이것과 비슷한걸 해봤는데 안됐다. [8:06 PM] 이것도 해보자.
hoyoul — 12/08/2023 8:33 PM 안된다.
hoyoul — 12/08/2023 9:01 PM https://stackoverflow.com/questions/77501290/unable-to-retrieve-google-id-token-in-flutter-web-always-returns-null
여기선 The issue is that googleSignIn.signIn() no longer returns an idToken on web. Switch over to googlesignIn.signInSilently(); [9:01 PM] 이런말이 나와서 혹했는데… [9:02 PM] 더이상 idToken을 return하지 않는다.
hoyoul — 12/08/2023 9:32 PM https://github.com/flutter/flutter/issues/33261
이것도 그럴듯해보여서 해봤는데, platform exception이 난다. [9:33 PM] 그리고 이미 google-services.json이 있는데…중복이 된다. [9:34 PM] 그리고 왜 default web client id인지도 이해가 안간다. [9:38 PM] 자고 싶어도 해결하지 못하면 잠이 안올꺼 같다.
hoyoul — 12/08/2023 9:47 PM 근데 실망인건, stack overflow의 답변들중, 그리고 채택된것도 모두 google sign in을 모르고 그냥 해봤더니 되더라는 식이다. 왜냐면 설명이 다 틀렸다. [9:47 PM] 설명을 해도 잘못된 설명을 하고 있다. [9:49 PM] 내가 옳다고 생각하는 방식이 안되면, 그다음으로 생각해야 하는것은 편법을 사용해야 한다. [9:49 PM] 편법이 있는지를 생각해야 하는데… [9:50 PM] 내부 코드에서 문제점을 확실히 캐치만 한다면 편법을 생각할수 있는데… [9:50 PM] 내가 실행하는 코드가 아니라서 제약이 많다. December 9, 2023
hoyoul — 12/09/2023 9:50 AM https://medium.com/@druhin.bala/google-signin-without-firebase-for-android-on-flutter-e3ee834f696e
이게 내생각과 동일하다. 현재 android에선 id token을 주지 않는다. [9:51 AM] 이사람이 택한 방식은 web으로 받아오는 건데… [9:52 AM] NOTE: It’s extremely important to choose the application type as “Web application”. Otherwise, Google will not return the attribute idToken when you call Google from your client application to sign in.
Google returns an idToken attribute with the iOS Client. However, it does not have the same behavior with the Android Client [9:53 AM] 여기 적혀 있듯이, google이 ios에서 대해서는 idToken을 넘기지만, android에게는 넘기지 않는다. 따라서 web application으로 접근해서 idToken을 받아오는 방식이다. [9:54 AM] 근데 이건 backend쪽에서 작업이 필요하다. [9:56 AM] android id token은 딱 2가지만 할것이다. [9:57 AM] 현재 android client id로 접근해서는 idtoken을 발급받지 못한다. 그런데 자연님은 android에서 id token을 받아서 처리한거 같다.
hoyoul — 12/09/2023 9:57 AM 이거 디버깅해보자. [9:59 AM] 그리고 그 다음에 web으로 token받아오는 방법을 생각해야 한다. [9:59 AM] 난 자연씨가 idtoken을 받았다고 생각하지 않긴하다.
hoyoul — 12/09/2023 10:18 AM 여튼 자연님의 소스를 다시 clone하자. [10:19 AM] git switch로 바꾸고 git pull하기엔, 내가 지금 작업한게 많다. 그냥 새롭게 받는게 낫다. [10:24 AM] 엥… [10:24 AM] android는 안되네..
hoyoul — 12/09/2023 10:25 AM ios야..당연히 되는거니까..볼 필요도 없는데… [10:26 AM] 내가 필요한건 android에서 idtoken가져오는건데… [10:27 AM] 원래 emulator하고 simulator를 login과정에서 사용할 수 없다고 나한테 말한거 봐서는..시료에서 되나? [10:27 AM] 시료에서 테스트해보고 bp걸어보자. [10:29 AM] 안드로이드폰에서 터치가 없으면 대기모드?로 너무 빨리 빠진다. [10:29 AM] 이거 어떻게 설정하지… [10:29 AM] 찾아보자.
hoyoul — 12/09/2023 10:33 AM 잠금시간이란게 있네 [10:37 AM] 5분으로 바꿨다.
hoyoul — 12/09/2023 10:47 AM 2가지 문제가 있다. [10:47 AM] 시료가 허브를 통하면 연결이 안된다. [10:47 AM] wireless debugging은 된다. [10:48 AM] 물론 android는 developper settings에서 usb debugging을 on시켜줘야 하기 때문에 그런 처리는 이미 했다. [10:48 AM] 그리고 너무 느리다. [10:48 AM] 허브가 싸구려라서 그럴수도 있다. [10:48 AM] 직접연결하면 또 된다. [10:49 AM] 이제 시료에서 되는지 확인해보자. [10:50 AM] android studio에서 하자.
hoyoul — 12/09/2023 10:55 AM 안된다. [10:55 AM] Launching lib/main.dart on SM A908N in debug mode… Running Gradle task ‘assembleDebug’… Exception: Gradle build failed to produce an .apk file. It’s likely that this file was generated under /Users/hoyoul/Development/playgrounds/kholdem-front/build, but the tool couldn’t find it. [10:56 AM] gradle에서 apk만드는데 실패한 모양이다. [10:57 AM] 그러면 이게 commit했을 당시에는 동작이 되다가 그 이후 변경사항이 생겨서 안되는것일수도 있고, 애초부터 android는 안된것일수도 있다. [10:57 AM] 좀 찾아서 고쳐보자. [10:59 AM] 이게 flavor를 사용해서 run도 다르게 시켜야하는거 같다. [11:01 AM] productFlavors { dev { dimension ‘build-type’ applicationIdSuffix “.dev” resValue “string”, “app_name”, “kholdem_dev” }
staging { dimension ‘build-type’ applicationIdSuffix “.staging” resValue “string”, “app_name”, “kholdem_staging” }
prod {
dimension 'build-type'
resValue "string", "app_name", "kholdem"
}
[11:01 AM] 보면 3개의 flavor가 있다. 하는일은 res라는 폴더에 string.xml에서 app_name을 설정하는거 같다. 맞나?
hoyoul — 12/09/2023 11:02 AM 그건 아니다. [11:05 AM] applicationid란건 package명이다. app name은 또 다른다. [11:05 AM] package명은 io.kholdem.aaa처럼 package명이고, app name은 원래 코드에서 정해준다. [11:06 AM] 예를 들면, MainActivity에서 정해주거나, flutter에서 MaterialApp에서 title을 설정할수 있다. [11:07 AM] applicationId에 suffix를 하는건 io.kholdem.staging 이런식으로 build시에 [11:07 AM] package이름을 사용한다는 것이고, appName은 그닥 중요하진 않다. [11:07 AM] package이름이 중요한건, app배포나 gcp설정때 등록하니까 중요하긴하다. [11:07 AM] 위에서 별다른건 없다. [11:08 AM] 그냥 소스는 main폴더를 이용하고 [11:08 AM] 설정만 각각의 flavor에서 하고 [11:08 AM] build할때 다르게 한다. 왜냐 설정이 다르니까.. [11:08 AM] 맞나? 그냥 뇌피셜이다. [11:09 AM] 여튼 build할때 옵션을 dev로 해야 할꺼 같다.
hoyoul — 12/09/2023 11:11 AM graddle이 build하는 command? 라면, [11:12 AM] gradlew assembleDebug로 지금 되어 있어서 찾을수 없다고 하니, [11:12 AM] 이것을 바꾸면 될듯하다. [11:13 AM] android폴더에 보면 gradlew라는 실행파일이 있다. [11:13 AM] batch파일이다. [11:13 AM] 한번 까보자. [11:14 AM] 그냥 autotool같은거다. [11:16 AM] 엥 안된다. 내가 잘못생각하나… [11:16 AM] ./gradlew assembleDevDebug Starting a Gradle Daemon, 2 incompatible Daemons could not be reused, use –status for details WARNING:We recommend using a newer Android Gradle plugin to use compileSdk = 33
This Android Gradle plugin (7.2.0) was tested up to compileSdk = 32
This warning can be suppressed by adding android.suppressUnsupportedCompileSdk=33 to this project’s gradle.properties
The build will continue, but you are strongly encouraged to update your project to use a newer Android Gradle Plugin that has been tested with compileSdk = 33
> Task :google_sign_in_android:compileDebugJavaWithJavac warning: [options] source value 8 is obsolete and will be removed in a future release warning: [options] target value 8 is obsolete and will be removed in a future release warning: [options] To suppress warnings about obsolete options, use -Xlint:-options. 3 warnings
> Task :shared_preferences_android:compileDebugJavaWithJavac warning: [options] source value 8 is obsolete and will be removed in a future release warning: [options] target value 8 is obsolete and will be removed in a future release warning: [options] To suppress warnings about obsolete options, use -Xlint:-options. 3 warnings
> Transform classes.jar (project :google_sign_in_android) with DexingWithClasspathTransform ERROR:/Users/hoyoul/Development/playgrounds/kholdem-front/build/google_sign_in_android/intermediates/runtime_library_classes_jar/debug/classes.jar: D8: java.lang.NullPointerException: Cannot invoke “String.length()” because “<parameter1>” is null [11:17 AM] 에러가 난다. [11:17 AM] log가 길어서 잘랐닫.
hoyoul — 12/09/2023 11:19 AM staging도 해보자. [11:20 AM] ./gradlew assembleStagingDebug [11:20 AM] 에러가 난다. [11:20 AM] debug.keystore는 동일하게 해줬는데.. [11:21 AM] signing관련인가? [11:21 AM] 근데 왜 이렇게 했을까? [11:24 AM] flutter에도 flavor실행하는 방식을 찾아보자.
hoyoul — 12/09/2023 11:28 AM flutter run하면 기본적으로 debug flavor로 동작하기 때문에 동작이 되어야 정상이다. [11:28 AM] 즉 이건 코드문제다. [11:30 AM] android폴더가 있기 때문에 android폴더에서 android의 flavor를 이용하자…이런거 같긴한데… [11:30 AM] 우선 기본적인 동작이 안되는건 문제가 있는것이다. [11:31 AM] 여튼 자연님 코드에서 android를 실행하는건 모르겠다.
hoyoul — 12/09/2023 11:36 AM 이건 순상님한테 도와달라고 해야 하는데…오늘이 주말이라서.. [11:36 AM] 메시지만 남겨보자.