ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 운동시간을 기록하고 그래프를 통해 확인할 수 있는 애플리케이션
    Projects/Toy Projects 2021. 9. 23. 17:29

    프로젝트 이름


    프로젝트 일정

    • 2020.12.23 : Android Studio 설치 및 기본 환경설정
    • 2020.12.24 ~ 2020.12.27 : 키, 몸무게에 따른 BMI 수치 및 몸 상태 출력 기능 구현
    • 2020.12.28 ~ 2020.12.30 : Room 라이브러리를 활용한 데이터베이스 구현
    • 2020.12.31 ~ 2021.01.03 : 날짜별 운동시간에 대한 데이터 처리 로직(삽입, 삭제, 수정) 구현
    • 2021.01.04 ~ 2021.01.07 : 설정 메뉴에 대한 UI 구성 및 기능 구현
    • 2021.01.08 ~ 2021.01.13 : MPAndroidChart 라이브러리를 활용한 그래프 구현 및 테스트
    • 2021.01.14 ~ 2021.01.15 : 그래프 출력에 대한 오류 수정
    • 2021.01.16 : 최종 테스트
    • 2021.01.17 : UI 보완 및 마무리

    기술 스택

    • IDE
     - Android Studio
    • Language
     - Java
    • Library
     - Room
     - MPAndroidChart

    프로젝트 목적

    실제 사용할 수 있는 서비스를 제작해 본다.

    서비스의 기획부터 출시까지의 과정을 경험해 본다.

    Android Studio를 활용하여 애플리케이션을 제작한다.


    프로젝트 기능

    운동시간 데이터 입력 기능

    그래프를 통한 운동시간 데이터 출력 기능

    BMI 지수 측정 기능

    목표선 설정 기능

    그래프 스타일 설정 기능

    총 운동 일자 계산 기능

    평균 운동 시간 계산 기능

    운동시간 데이터 삭제 기능


    Room 라이브러리를 활용한 데이터베이스의 구현

    Room 이란?

    안드로이드 앱에서 SQLite 데이터베이스를 쉽고 편리하게 사용할 수 있도록 하는 기능이다. 사용자의 데이터를 로컬 데이터베이스에 저장하여 기기가 네트워크에 접근할 수 없을 때도 사용자가 콘텐츠를 탐색할 수 있다.

     

    1. Room의 구성요소 (Database, Entity, Dao)

    @Entity
    public class UserData {
       
        @PrimaryKey
        private int date;
        private int time;
    
    
        public UserData(int date, int time) {
            this.date = date;
            this.time = time;
        }
    
        public int getDate() {
            return date;
        }
    
        public void setDate(int date) {
            this.date = date;
        }
    
        public int getTime() {
            return time;
        }
    
        public void setTime(int time) {
            this.time = time;
        }
    
        @Override
        public String toString() {
            return "UserData{" +
                    "date=" + this.date +
                    ", time=" + this.time +
                    '}' + "\n";
        }
    }

    ㆍ Room을 구성하는 3가지 주요 요소 중 하나인 Enitity에 관한 클래스이다.
    ㆍ 데이터베이스에서 테이블 역할을 수행하며, 데이터베이스에 저장되는 데이터를 정의한 클래스이다.
    ㆍ "@Entity" 애너테이션을 클래스 상단에 작성함으로써, Entity 역할을 하는 클래스임을 명시한다.
    ㆍ "@PrimaryKey" 애너테이션으로 기본 키 값을 지정할 수 있으며, 기본 키는 중복될 수 없다.

     

    @Dao
    public interface UserDataDao {
      
        @Query("SELECT * FROM UserData")
        List<UserData> getAll();
    
        @Query("DELETE FROM UserData")
        void deleteAll();
    
        @Insert(onConflict = OnConflictStrategy.REPLACE)
        void put(UserData userData);
    
        @Update
        void update(UserData userDate);
    
        @Delete
        void delete(UserData userData);
    }

    ㆍ Room을 구성하는 3가지 주요 요소 중 하나인 DAO에 관한 인터페이스이다.
    ㆍ 데이터베이스에 접근하여 수행할 작업을 메서드 형태로 정의하였다.

     

    @androidx.room.Database(entities = {UserData.class}, version = 1)
    public abstract class Database extends RoomDatabase {
        public abstract UserDataDao userDataDao();
    }

    ㆍ Room을 구성하는 3가지 주요 요소 중 하나인 Database에 관한 인터페이스이다.
    ㆍ 데이터베이스를 새롭게 생성하거나 버전을 관리하기 위한 클래스이다.
    ㆍ 해당 인터페이스는 RoomDatabase 클래스를 상속받는 추상 클래스여야 한다.
    ㆍ "@Database" 애너테이션을 통해 Database 역할을 하는 인터페이스임을 명시해야 하며, 애너테이션 안에 해당 데이터베이스와 관련된 Entity 리스트를 포함해야 한다.

     

    2. 사용자의 데이터를 데이터베이스에 삽입

    saveData.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (TextUtils.isEmpty(putDate.getText()) || TextUtils.isEmpty(putTime.getText())) {
                        Toast.makeText(getApplicationContext(), R.string.informationMessage, Toast.LENGTH_SHORT).show();
                    } else {
                        try {
                            db.userDataDao().put(new UserData(Date.getDate(putDate), Time.getTime(putTime)));
                        } catch (ParseException e) {
                            e.printStackTrace();
                        }
                        Toast.makeText(getApplicationContext(), R.string.saveMessage, Toast.LENGTH_SHORT).show();
    
                        putDate.setText("");
                        putTime.setText("");
                    }
                }
            });

    ㆍ 위 코드는 Register_Activity.java 파일에서 saveData 버튼을 눌렀을 때 동작을 정의한 메서드이다.
    ㆍ 사용자의 운동 일자와 운동시간에 관한 데이터를 Entity로써 정의한 UserData 객체 형태로 생성한다.
    ㆍ 그 후, DAO 인터페이스에 정의한 put( ) 메서드를 통해 데이터베이스에 데이터를 삽입한다.


    MPAndroidChart 라이브러리를 활용한 꺾은선 그래프의 구현

    MPAndroidChart 란?

    Philipp Jahoda가 개발한 애플리케이션을 위한 차트 라이브러리이다. 다양한 종류의 그래프들과 그래프를 제어할 수 있는 다양한 이벤트들을 제공해주고 있다.

     

    1. 라이브러리 추가

    implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0'

    ㆍ build.gradle 파일의 dependencies 부분에 위 코드를 작성하여 MPAndroidChart 라이브러리를 추가해준다.

     

    2. Chart 태그 추가

    <com.github.mikephil.charting.charts.LineChart
            android:id="@+id/graph"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:layout_marginBottom="8dp"
            app:layout_constraintBottom_toTopOf="@+id/graphMenuButton"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

    ㆍ 자신이 사용하고 싶은 레이아웃 파일에 Chart 태그를 작성해준다.
    ㆍ LineChart 외에도 다양한 Chart를 사용할 수 있다.

     

    3. 데이터 생성

    ArrayList<Entry> values = new ArrayList<>();   // lineDataSet에 담겨질 데이터를 ArrayList 형태로 선언
    
    /** lineDataSet에 담겨질 사용자의 운동시간 정보를 values에 삽입 */
    for (int i = 0; i < userDataList.size(); i++) {
       int time = userDataList.get(i).getTime();
    
       values.add(new Entry(i, time));
    }
    
    if (values.isEmpty())   // values에 담겨진 데이터가 없을 경우 확대 및 축소 불가능
       getGraph.setScaleEnabled(false);
    else   // values에 담겨진 데이터가 있을 경우 확대 및 축소 가능
       getGraph.setScaleEnabled(true);

    ㆍ 사용자의 운동시간 정보를 저장할 ArrayList 변수인 values를 선언한다.
    ㆍ 이때 ArrayList의 타입은 라이브러리에서 지정한 Entry 타입이어야 한다.
    ㆍ 그 후, 사용자의 운동시간 정보를 values 변수에 삽입함으로써 그래프로 출력할 데이터 리스트를 생성한다.

     

    4. LineDataSet 생성

    LineDataSet lineDataSet = new LineDataSet(values, "Exercise Time");   // ArrayList인 values를 참조해 LineDataSet 선언
    lineDataSet.setColor(ContextCompat.getColor(context, R.color.easypink));   // 사용자에게 출력될 차트의 색상 설정
    lineDataSet.setCircleColor(ContextCompat.getColor(context, R.color.easypink));   // 사용자에게 출력될 차트의 구분점 테두리 색상 설정
    lineDataSet.setCircleHoleColor(ContextCompat.getColor(context, R.color.easypink));   // 사용자에게 출력될 차트의 구분점 구멍 색상 설정
    lineDataSet.setDrawFilled(true);
    Drawable graphFill = ContextCompat.getDrawable(context, R.drawable.graph_filled);   // 사용자에게 출력될 차트의 색상이 그라데이션으로 출력되도록 설정
    lineDataSet.setFillDrawable(graphFill);

    ㆍ 사용자에게 출력될 정보가 담긴 values를 참조해 lineDataSet을 선언한다.
    ㆍ LineDataSet 변수는 사용자에게 보일 하나의 그래프에 대한 변수이다.
    ㆍ 그래프 선의 색상, 구분점의 색상 등 다양한 설정이 가능하다.

     

    5. LineData 생성

    LineData lineData = new LineData();   // LineDataSet을 담는 그릇으로써 여러 개의 LineDataSet 삽입 가능
    lineData.addDataSet(lineDataSet);   // lineData에 위에서 선언한 lineDataSet 삽입
    lineData.setValueTextColor(ContextCompat.getColor(context, R.color.lightblack));   // 사용자에게 출력될 차트의 텍스트 색상 설정
    lineData.setValueTextSize(9);   // 사용자에게 출력될 차트의 텍스트 사이즈 설정

    ㆍ LineDataSet을 담는 그릇과 같은 역할을 하는 LineData 변수를 생성한다. 이때 여러 개의 LineDataSet을 삽입할 수 있다.
    ㆍ 그래프의 텍스트 색상, 사이즈 등 다양한 설정이 가능하다.

     

    6. x축 설정

    XAxis xAxis = getGraph.getXAxis();   // x축에 해당하는 변수인 xAxis 선언
    xAxis.setPosition(XAxis.XAxisPosition.BOTTOM);   // x축의 위치 설정
    xAxis.setTextColor(ContextCompat.getColor(context, R.color.lightblack));   // x축에 표시될 텍스트 색상 설정
    xAxis.setGridColor(ContextCompat.getColor(context, R.color.superlightblack));   // x축의 색상 설정
    xAxis.setGranularityEnabled(true);   // x축 데이터들의 간격 설정
    xAxis.setGranularity(1f);   // x축 데이터들 간의 간격을 1f로 설정
    xAxis.setLabelCount(5);   // x축에 표시될 구간을 최대 5개로 설정

    ㆍ x축에 해당하는 변수인 xAxis를 선언한다.
    ㆍ x축에 관련된 다양한 설정이 가능하다.

     

    7. x축 데이터 재가공

    final ArrayList<String> xLabelList = new ArrayList<String>();   // x축에 표시될 데이터들을 ArrayList 형태로 선언
    
    /** 데이터베이스에 저장된 사용자의 운동 일자 정보를 위에서 선언한 xLabelList에 추가 */
    for (int i = 0; i < userDataList.size(); i++) {
       SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyMMdd");
       Date date = null;
          
       try {
          date = simpleDateFormat.parse(Float.toString(userDataList.get(i).getDate()));
       } catch (ParseException e) {
          e.printStackTrace();
       }
    
       SimpleDateFormat newFormat = new SimpleDateFormat("MM/dd");
       String dateString = newFormat.format(date);
    
       xLabelList.add(dateString);
    }
    
    /** x축에 표시될 데이터(xLabelList)를 재가공하여 출력 */
    xAxis.setValueFormatter(new ValueFormatter() {
    
       @Override
       public String getFormattedValue(float value) {
          if (xLabelList.size() == 1) {
             if (value < 0)
                return "";
             else if (value == 1)
                return "";
             else
                return xLabelList.get((int) value);
           } else
                return xLabelList.get((int) value);
       }
    });

    ㆍ x축에 사용자의 운동 날짜 정보를 출력하기 위해 재가공하는 코드이다.
    ㆍ x축에 표시될 데이터 리스트인 String 타입의 ArrayList 변수 xLabelList를 선언한다.
    ㆍ 데이터베이스에 저장된 운동 날짜 데이터를 SimpleDataFormat( ) 메서드를 이용하여 적절한 날짜 형태로 변환 후 xLabelList에 추가한다.
    ㆍ 최종적으로 getFormattedValue( ) 메서드를 String 타입의 날짜 형태로 출력하도록 재정의 한다.

     

    8. y축 설정

    YAxis yAxisLeft = getGraph.getAxisLeft();   // 좌측 y축에 해당하는 변수인 yAxisLeft 선언
    yAxisLeft.setTextColor(ContextCompat.getColor(context, R.color.lightblack));   // y축에 표시될 텍스트 색상 설정
    yAxisLeft.setGridColor(ContextCompat.getColor(context, R.color.lightblack));   // y축의 색상 설정
    yAxisLeft.setAxisMinimum(0f);   // y축에 표시될 최소 범위 설정
    yAxisLeft.setAxisMaximum(160f);   // y축에 표시될 최대 범위 설정
    yAxisLeft.setLabelCount(4);
    
    YAxis yAxisRight = getGraph.getAxisRight();   // 우측 y축에 해당하는 변수인 yAxisRight 선언
    yAxisRight.setDrawLabels(false);   // 우측 y축에 대한 미사용 설정
    yAxisRight.setDrawAxisLine(false);   // 우측 y축에 대한 미사용 설정
    yAxisRight.setDrawGridLines(false);   // 우측 y축에 대한 미사용 설정

    ㆍ 좌측 y축에 해당하는 변수인 yAxisLeft를 선언한 후 색상, 범위 등 다양한 설정을 한다.
    ㆍ 우측 y축은 사용하지 않을 예정이므로 미사용 설정을 한다.

     

    9. Legend 설정

    Legend legend = getGraph.getLegend();   // 레전드에 해당하는 변수인 legend 선언
    legend.setEnabled(false);   // 레전드에 대한 미사용 설정

    ㆍ 그래프 하단에 색과 라벨을 나타내는 Lf에 대한 설정이다.
    ㆍ Legend는 사용하지 않을 예정이므로 미사용에 대한 설정을 한다.

     

    10. Description 설정

    Description description = graph.getDescription();   // 디스크립션에 해당하는 변수인 description 선언
    description.setEnabled(false);   // 디스크립션에 대한 미사용 설정

    ㆍ 그래프 하단에 설명을 나타내는 Description에 대한 설정이다.
    ㆍ Description은 사용하지 않을 예정이므로 미사용에 대한 설정을 한다.

     

    11. 그래프 삽입

    getGraph.setData(lineData);   // 위에서 설정한 그래프를 최종적으로 삽입

    ㆍ 위에서 설정한 그래프를 setData( ) 메서드를 사용하여 삽입한다.


    출시

     

    꾸준하게 - Google Play 앱

    이 어플리케이션은 운동 기록을 포함한 다양한 기능들을 제공합니다.

    play.google.com

     

    728x90

    댓글

Designed by Tistory.