본문 바로가기

Android

Android에서 Riot API 를 이용해 롤 전적 검색 어플 만들기 - #3. WebView 개발 1

0. 시작하기 전에..

늦어서 죄송합니다... 기다리시는 분들이 많으셨을텐데... 제가 개인 프로젝트만 혼자 6개를 동시에 진행 중이고 롤 전적 검색 어플 개발 마무리를 하느라 조금 시간이 걸렸습니다.

 

시국이 시국인지라 어수선해서 글도 오랜만에 쓰네요. 다들 코로나 바이러스 조심하시기 바랍니다.

 

해당 롤 전적 검색 어플WebView(웹)/Android 기반으로 되어있기 때문에 html/css/javascript(jquery/ajax)에 대한 기본적인 지식안드로이드 앱 개발에 대한 전반적인 지식을 요구합니다.

 

일반적인 라이엇의 Java support API 또는 안드로이드 지원 API 를 통한 전적 검색 어플 개발을 생각하신 분은 뒤로가기를 눌러주시면 감사하겠습니다.

 

아직 어플을 완성한 것은 아니지만 어플 자체의 최종 버전은 https://github.com/shyunku/LOLhelper/tree/android 에서 확인하실 수 있습니다.

 

동시에 같은 트리의 마스터 브랜치인 https://github.com/shyunku/LOLhelper 에서 해당 어플에 들어가는 WebView를 보실 수 있습니다.

 

굳이 분리한 이유는, 안드로이드 자체 내에서 웹개발을 진행하면 수정/내용 확인이 굉장히 귀찮고 번거롭기 때문임.

 

마스터 브랜치의 index.html를 Visual Code에서 오른쪽 클릭하여 Open with Live Server 를 눌러주면 라이브 서버에서 실행되는 webView를, 코드가 수정되는 즉시 적용되는 것을 볼 수 있다. (페이지 새로고침 필요)

Live Server in Visual Code

설명 안 한 것이 있는데, 깃허브에 안드로이드든 웹 쪽이든 credentials.json이라는 파일이 있을 것이다.

 

해당 파일은 아래와 같은 형식으로 되어있는데, 

{
    "riot_api_key": "",
    "my_account_puuid": ""
}

위의 칸에는 라이엇에서 제공한 임시 또는 정식 API KEY를 넣으면 되고, puuid에는 자신의 계정 puuid를 넣도록 한다.

 

굳이 따로 credential 파일을 만든 이유는 이 정보들이 민감한 데이터이기 때문에, 깃허브에 올리거나 할 때 노출되지 않도록 한 것이다.

 

API KEY나 비밀번호 등은 따로 저장하는 것이 좋다. 깃허브의 경우엔 커밋 전 .gitignore 목록에 해당 파일을 추가하도록 하자.

 

애초에 이 앱은 자신의 전적 기록 정보로 시작하고, 이후 검색으로 다른 소환사의 정보를 찾는 형식이므로 계정 puuid가 필요하다.

 

그리고 자신의 puuid는 라이엇 개발자 홈페이지에서 알 수 있다.

 

https://developer.riotgames.com/apis#summoner-v4

 

Riot Developer Portal

 

developer.riotgames.com

위 페이지에서는 소환사 정보를 json 형태로 가져올 수 있는데, 

 

위에서 2번째 항목

2번째 항목에 소환사 이름(닉네임)으로 소환사 정보 가져오기 탭이 있다.

 

확장하여 내린 다음 자신의 닉네임을 치고 REGION을 자신의 서버 지역으로 선택한다.

 

나는 KR서버에서 게임을 하기 때문에 KR로 선택했다.

 

그리고 밑의 EXECUTE REQUEST를 클릭하면 라이엇에서 RESPONSE가 날라오고 밑에 표시된다.

 

RESPONSE CODE가 200이라면 잘 된 것이다.

 

그 중 RESPONSE BODY를 살펴보면 아래와 같은 정보가 출력된다.

 

  • profileIconId - 프로필 아이콘 이미지 ID

  • name - 소환사 닉네임

  • puuid - 해당 계정의 절대 불변하지 않는 고유 식별 ID (라고 하는데, 내 경우에는 API KEY가 바뀌니까 바뀌었다 (?))

  • summonerLevel - 소환사 레벨

  • accountId - 계정 ID, 아마도 닉네임 변경을 할 시 바뀌는 것 같다.

  • id - 이것도 ID...? 계정 ID와 별개임, 구분을 잘해야 함

  • revisionDate - 최종 수정된 날짜?가 long 형태의 timestamp로 저장된 것 같은데, 확실하게 무엇인지는 잘 모른다.

이 곳에서 puuid를 확인하고 credential 파일에 넣도록 하자.

 

1. WebView에 넣을 웹페이지 제작

안드로이드에는 WebView라는 것이 있다. RecyclerView나 TextView와 같이 View의 역할을 한다.

 

WebView는 웹 형식으로 된 웹 페이지(html+css+js+...)를 안드로이드에 구겨넣을 수 있도록 하는 View라고 할 수 있다. (필자는 이 것을 만들면서 이런 것이 있다는 것을 처음 알았다..)

 

그러니까 롤 전적 검색 어플이 아니더라도 html이나 css를 통한 디자인과 자바스크립트가 안드로이드의 자바보다 더 친숙하신 분들은 웹 기반으로 어플을 개발할 수 있는 것이다.

 

아무튼 시작해보도록 하자.

 

형식은 내가 개발한 깃허브의 코드를 참조하면서 설명하겠다.

 

설명 전 2020년 3월 24일 현재 기준 내 앱의 5가지 단점을 먼저 설명하겠다. 수정 후 깃허브에 올린 내용은 지움 표시하겠음

  • 최근 전적 기록 보여줄 때 최근 전적 통계가 자세히 나오지 않음, 승률만 나와있음 - 추후에 추가 예정

  • 최근 전적 기록 중 팀 정보에 티어/레벨이 나오지 않음 - 일부러 막아 놓은 것인데, home.js의 901번째 정도의 줄에서 getAndLoadParticipantsLeagueInfoBySummonerID() 이부분을 주석 처리 해제하면 일부 로드되긴 하지만, 라이엇 API에서의 단위 시간당 Request 제한 때문에 데이터가 끊겨서 그냥 주석 처리해 놓았다. 해결 방법을 아시는 분은 댓글 바랍니다.

  • 딜량 확인 탭에서 딜량 그래프가 갑자기 제대로 표시되지 않음 - 원래는 잘 되었는데, 안드로이드에 넣으면서 뭔가 이상해졌다. 추후에 수정 가능한 사항

  • 해당 소환사가 게임 중일 경우 게임 정보가 나오지 않음 - 추후 수정 가능함

  • 안드로이드에서 View가 맞지 않아 이상하다. View의 Height도 그렇고, 최근 전적과 숙련도 탭의 높이 차이도 조치를 취해야하는데 아직은 어떻게 해야할지 잘 모르겠다.

 

 

1-1. 프론트엔드 디자인

나는 이와 같은 웹 개발을 할 때 프론트엔드에서 디자인을 먼저 하고, 백엔드에서 데이터를 가져와 프론트엔드의 값에 채워넣어주는 작업 형식으로 진행하므로 각자의 방식대로 따라오기 바람.

 

밑의 코드는 index.html의 전문이다.

<!DOCTYPE html>
<html>
    <head>
        <title>title</title>
        <meta name="viewport" content="width=device-width">
        <link rel="stylesheet" href="reset.css">
        <link rel="stylesheet" href="style.css">
        <link rel="stylesheet" href="clear.css">
        <link href="https://fonts.googleapis.com/css?family=Noto+Sans+KR:100,400,700&display=swap" rel="stylesheet">
        <script src="lib/jquery-3.4.1.js"></script>
        <script src="lib/chart.js"></script>
        <script src="lib/jquery-ui.js"></script>
        <script src="home.js"></script>
        <script src="lib/jquery.ajax-cross-origin.min.js"></script>
    </head>
    <body>
        <div class="body-content" id="body_content">
            <div class="searcher">
                <input id="search_summoner_input" placeholder="소환사명을 입력하세요..">
            </div>
            <div class="user-repr-info">
                <div class="profile-icon-wrapper">
                    <img id="current_summoner_profile_icon_img" src="http://ddragon.leagueoflegends.com/cdn/9.24.2/img/profileicon/0.png">
                    <span class="summoner-level" id="current_summoner_level">-</span>
                </div>
                <div class="user-repr-info-wrapper">
                    <span class="summoner-name" id="current_summoner_name">SummonerName</span>
                    <div class="info-detail-wrapper" id="tier_info_detail_wrapper">
                    </div>
                </div>
            </div>
            <div class="info-tab-container">
                <div style="grid-column: 1;" class="info-tab" id="recent_game_history_info_tab">
                    <span>최근 전적</span>
                </div>
                <div style="grid-column: 2;" class="info-tab" id="mastery_info_tab">
                    <span>숙련도</span>
                </div>
                <div style="grid-column: 3;" class="info-tab" id="current_game_info_tab">
                    <span>현재 게임 정보</span>
                </div>
            </div>
            <div class="main-content">
                <div class="recent-game-info main-info-tab" id="recent_game_info_container">
                    <div class="game-history-summary-container" id="game_history_summary_container">
                        <div class="recent-game-summary-chart-wrapper">
                            <canvas class="win-rate-pie-chart" id="win_rate_pie_chart"></canvas>
                            <div id="win_rate_chart_desc">
                                <span id="user_win_num"></span>
                                <span id="user_lose_num"></span>
                            </div>
                            <span id="win_rate_chart_percentage"></span>
                        </div>
                    </div>
                    <div class="game-history-list" id="game_history_list_container">
                        <!-- <div class="game-history-item-wrapper summoners-rift folded" id="game_history_item_wrapper">
                            <div class="game-history-item lose-type" id="game_history_item">
                                <div class="item-wrapper">
                                    <div class="item-detail-1">
                                        <span class="map-type">Example</span>
                                        <span class="win-or-lose">패배</span>
                                        <span class="timelapse">5분 전</span>
                                    </div>
                                    <div class="item-detail-2">
                                        <div class="champ-wrapper">
                                            <div class="upper-div">
                                                <div class="main-champion-illust-wrapper">
                                                    <div class="last-champion-level">23</div>
                                                </div>
                                            </div>
                                            <div class="spell-wrapper">
                                                <div class="mid-container">
                                                    <div class="spell-img"></div>
                                                    <div class="spell-img"></div>
                                                </div>
                                            </div>
                                            <div class="spell-wrapper">
                                                <div class="mid-container">
                                                    <div class="rune-img"></div>
                                                    <div class="rune-img"></div>
                                                </div>                                
                                            </div>
                                            <div class="champion-name">
                                                <span>아트록스</span>
                                            </div>
                                        </div>
                                    </div>
                                    <div class="item-detail-3">
                                        <div class="KDA-wrapper">
                                            <div class="KDA-score">-4.00</div>
                                            <div class="KDA">
                                                <span class="kill">5</span>
                                                <span class="slash">/</span>
                                                <span class="death">2</span>
                                                <span class="slash">/</span>
                                                <span class="assist">3</span>
                                            </div>
                                        </div>
                                    </div>
                                    <div class="item-detail-5">
                                        <div class="gold-wrapper">
                                            <span>12,543 G</span>
                                        </div>
                                        <div class="cc-wrapper">
                                            <span>CC</span>
                                            <span>25s</span>
                                        </div>
                                        <div class="cs-wrapper">
                                            <span>CS</span>
                                            <span class="total-cs">63</span><span class="average-cs">(2.5)</span>
                                        </div>
                                    </div>
                                    <div class="item-detail-4">
                                        <div class="item-wrapper">
                                            <div class="item-item"></div>
                                            <div class="item-item"></div>
                                            <div class="item-item"></div>
                                            <div class="item-item"></div>
                                            <div class="item-item"></div>
                                            <div class="item-item"></div>
                                            <div class="item-item"></div>
                                        </div>
                                    </div>
                                </div>
                            </div>
                            <div class="game-history-item-description-tab" id="game_history_item_desc">
                                <div class="item-detail-menu-list-tab">
                                    <div class="detail-menu-list-tab general-info focused" style="grid-column: 1;" id="general_match_info_tab">
                                        <span>일반 정보</span>
                                    </div>
                                    <div class="detail-menu-list-tab deal-amount-info unfocused" style="grid-column: 2;" id="deal_amount_info_tab">
                                        <span>딜량 확인</span>
                                    </div>
                                </div>
                                <div class="item-detail-desc-content-wrapper">
                                    <div class="item-detail-desc-content">
                                        <div class="team-desc-label-wrapper win">
                                            <span class="team-desc-win-or-lose">승리</span>
                                            <span class="team-label">블루 팀</span>
                                        </div>
                                        <div class="participant-info-container win">
                                            <div class="participant-info">
                                                <div class="participant-detail-info-1">
                                                    <div class="participant-champion-img"></div>
                                                </div>
                                                <div class="participant-detail-info-2">
                                                    <div class="participant-summoner-spell-wrapper">
                                                        <div class="participant-spell"></div>
                                                        <div class="participant-spell"></div>
                                                    </div><div class="participant-summoner-perk-wrapper">
                                                        <div class="participant-perk"></div>
                                                        <div class="participant-perk"></div>
                                                    </div>
                                                </div>
                                                <div class="participant-detail-info-3">
                                                    <div class="participant-info-wrapper">
                                                        <span class="participant-username">nickname</span>
                                                        <span class="participant-tier-level">그랜드마스터 IV</span>
                                                    </div>
                                                </div>
                                                <div class="participant-detail-info-4">
                                                    <div class="participant-kda-wrapper">
                                                        <span class="participant-kda-score">2.52</span>
                                                        <span class="participant-kda">
                                                            <span class="kill">5</span>
                                                            <span class="slash">/</span>
                                                            <span class="death">2</span>
                                                            <span class="slash">/</span>
                                                            <span class="assist">3</span>
                                                        </span>
                                                    </div>
                                                </div>
                                                <div class="participant-detail-info-5 pulled-deal-container">
                                                    <div class="participant-cs-wrapper">
                                                        <span class="participant-gold">612,563 G</span>
                                                        <span class="participant-cs">CS 325(15.6)</span>
                                                    </div>
                                                </div>
                                                <div class="participant-detail-info-6 pulled-deal-container">
                                                    <div class="participant-item-wrapper">
                                                        <div class="participant-item"></div>
                                                        <div class="participant-item"></div>
                                                        <div class="participant-item"></div>
                                                        <div class="participant-item"></div>
                                                        <div class="participant-item"></div>
                                                        <div class="participant-item"></div>
                                                        <div class="participant-item"></div>
                                                    </div>
                                                </div>
                                                <div class="participant-detail-info-7 pushed-deal-container">
                                                    <div class="deal-amount-wrapper">
                                                        <span class="total-dealt-amount">-</span> (
                                                        <span class="physical-dealt-amount">-</span> /
                                                        <span class="magical-dealt-amount">-</span> /
                                                        <span class="true-dealt-amount">-</span> )
                                                    </div>
                                                    <div class="max-dealt-bar">
                                                    </div>
                                                </div>
                                            </div>
                                        </div>
                                        <div class="team-desc-label-wrapper lose">
                                            <span class="team-desc-win-or-lose">패배</span>
                                            <span class="team-label">레드 팀</span>
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </div> -->
                    </div>
                </div>
                <div class="mastery-info main-info-tab" id="mastery_info_container">
                    <div class="mastery-summary">
                        <div class="total-mastery">
                            <span id="total_mastery_label">숙련도 총합</span>
                            <span id="total_mastery">- 점</span>
                        </div>
                        <div class="detail-mastery">
                            <div class="mastery-amount-box">
                                <img src="img/mastery/Champion_Mastery_Level_7_Flair.png" class="champion_mastery_img">
                                <span class="champion-mastery-level-amount" id="champion_mastery7_total_value">-</span>
                            </div>
                            <div class="mastery-amount-box">
                                <img src="img/mastery/Champion_Mastery_Level_6_Flair.png" class="champion_mastery_img">
                                <span class="champion-mastery-level-amount" id="champion_mastery6_total_value">-</span>
                            </div>
                            <div class="mastery-amount-box">
                                <img src="img/mastery/Champion_Mastery_Level_5_Flair.png" class="champion_mastery_img">
                                <span class="champion-mastery-level-amount" id="champion_mastery5_total_value">-</span>
                            </div>
                            <div class="mastery-amount-box">
                                <img src="img/mastery/Champion_Mastery_Level_4_Flair.png" class="champion_mastery_img">
                                <span class="champion-mastery-level-amount" id="champion_mastery4_total_value">-</span>
                            </div>
                            <div class="mastery-amount-box">
                                <img src="img/mastery/Champion_Mastery_Level_3_Flair.png" class="champion_mastery_img">
                                <span class="champion-mastery-level-amount" id="champion_mastery3_total_value">-</span>
                            </div>
                        </div>
                        <div class="champion-mastery-list" id="champion_mastery_list">
                        </div>
                    </div>
                </div>
                <div class="current-game-info main-info-tab" id="current_game_info_container">
                    <!-- current game doesn't exist -->
                    <div class="not-playing-now-msg-container" id="not_playing_now_container">
                        <span class="not-playing-title">해당 플레이어는<br>현재 게임 중이 아닙니다.</span>
                    </div>
                    <div class="current-game-content-wrapper" id="current_game_info_content_wrapper">
                        <!-- <div class="current-game-content">
                            <span class="current-game-map-type">칼바람 나락</span>
                            <span class="current-game-elapsed-time" id="current_game_elapsed_time">17분 33초</span>
                            <div class="current-game-participant-info-container">
                                <div class="current-team-info-container blue-team">
                                    <div class="team-info-label">
                                        <span>블루 팀</span>
                                    </div>
                                    <div class="team-info-container">
                                    </div>
                                </div>
                                <div class="current-team-info-container red-team">
                                    <div class="team-info-label">
                                        <span>레드 팀</span>
                                    </div>
                                    <div class="team-info-container">

                                    </div>
                                </div>
                            </div>
                        </div> -->
                    </div>
                </div>
            </div>
        </div>
    </body>
</html>

다시 말하지만 나는 독학으로 공부했기 때문에 웹개발에 대해 expert가 아니다.

 

중간중간 쓸데없는 div가 있을 수 있고 안 좋은 습관이 있을 수 있는데 프론트엔드는 각자만의 방식대로 만들면 된다.

 

이는 html뿐 아니라 css/js도 마찬가지다, 잘 돌아가기만 하면 된다. (다만 안 좋은 습관은 고치도록 해보자)

 

중간에 주석처리된 부분이 있는데, 이는 첫 프론트엔드 작업 시 작업을 시각화하기 위한 임의로 만든 포맷이고 이후 js에서 같은 형식으로 div에 append하는 식이라 참고만 하고 넘어가면 된다.

 

내 코드는 실제로 실행해보면 다음과 같이 나오게 된다.

이외의 css 파일 설명은 스킵하겠다.

 

1-2. 백엔드 스크립트 작성

사실 따지고보면 Backend 작업이 아니라 프론트엔드에서의 스크립트 작성이다.

 

그래도 자체 로컬 서버에서 돌리는거니 백엔드라고 하자.

 

home.js에서 거의 모든 동작들이 이루어지는데, 너무 길어질 것 같으니 다음 포스팅에서 이어하겠다.