이번에는 경남드리의 부가기능과 그것을 구현하면서 배운 점에 대해 적어보려고 한다.
주요한 경남드리의 부가기능에는 회원가입, 좋아요 및 리뷰가 있다. 

1)회원가입 

회원가입은 FireBase의 공식문서를 참고하여 만들었다. 
https://firebase.google.com/docs/auth/android/start?hl=ko&authuser=2

Android에서 Firebase 인증 시작하기     |  Firebase Authentication

Google I/O 2023에서 Firebase의 주요 소식을 확인하세요. 자세히 알아보기 의견 보내기 Android에서 Firebase 인증 시작하기 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하

firebase.google.com

이 문서에 보면 신규 사용자 가입이라는 항목이 있다. 이 항목 코드를 가져와서 사용해주는데 만약 이름이나 사진을 넣어주고 싶다면 가입하는 코드 내에서 회원가입이 정상적으로 이루진 후에  유저의 정보에 이름과 사진을 추가해주는 코드가 들어가도록 구현해주면 된다. 
 

mAuth.createUserWithEmailAndPassword(email, password)
        .addOnCompleteListener(this, new OnCompleteListener<AuthResult>() {
            @Override
            public void onComplete(@NonNull Task<AuthResult> task) {
                if (task.isSuccessful()) {
                    // Sign in success, update UI with the signed-in user's information
                    Log.d(TAG, "createUserWithEmail:success");
                    FirebaseUser user = mAuth.getCurrentUser();
                    updateUI(user);
                } else {
                    // If sign in fails, display a message to the user.
                    Log.w(TAG, "createUserWithEmail:failure", task.getException());
                    Toast.makeText(EmailPasswordActivity.this, "Authentication failed.",
                            Toast.LENGTH_SHORT).show();
                    updateUI(null);
                }
            }
        });

 

 

이 email과 password에 우리의 값을 넣어서 isSuccessful 시에 회원가입 시 성공인 화면이 나올 수 있도록 만들어 보겠다. 
 
일단 회원가입에 유저정보를 받을 수 있게 틀을 xml로 작성해주자 

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#AED2FF">


    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">

            <TextView
                android:id="@+id/back"
                android:layout_width="50dp"
                android:layout_height="50dp"
                android:layout_marginLeft="20dp"
                android:layout_marginTop="20dp"
                android:background="@drawable/backspaceimage"
                android:textColor="@color/purple_700"
                android:textSize="20dp" />

            <TextView
                android:id="@+id/signup"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="20dp"
                android:layout_marginBottom="20dp"
                android:layout_weight="2"
                android:gravity="center"
                android:text="회원 가입"
                android:textColor="@color/purple_700"
                android:textSize="25dp" />
        </LinearLayout>

        <ScrollView
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginLeft="30dp"
                android:layout_marginTop="10dp"
                android:layout_marginRight="30dp"
                android:orientation="vertical">


                <TextView
                    android:id="@+id/signIDtext"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginBottom="5dp"
                    android:text="닉네임"
                    android:textColor="@color/purple_700"
                    android:textSize="20dp"
                    android:textStyle="bold" />

                <EditText
                    android:id="@+id/signID"
                    android:layout_width="wrap_content"
                    android:layout_height="40dp"
                    android:layout_marginBottom="20dp"
                    android:background="@color/white"
                    android:ems="10"
                    android:hint=" 닉네임"
                    android:inputType="textPersonName"
                    android:text=""
                    android:textColor="@color/black"
                    android:textColorHint="#60000000" />


                <TextView
                    android:id="@+id/signmailtext"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginBottom="5dp"
                    android:text="이메일"
                    android:textColor="@color/purple_700"
                    android:textSize="20dp"
                    android:textStyle="bold" />

                <EditText
                    android:id="@+id/signmail"
                    android:layout_width="wrap_content"
                    android:layout_height="40dp"
                    android:layout_marginBottom="20dp"
                    android:background="@color/white"
                    android:ems="10"
                    android:hint="이메일"
                    android:inputType="textPersonName"
                    android:text=""
                    android:textColor="@color/black"
                    android:textColorHint="#60000000" />

                <TextView
                    android:id="@+id/signPWtext"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginBottom="5dp"
                    android:text="비밀번호"
                    android:textColor="@color/purple_700"
                    android:textSize="20dp"
                    android:textStyle="bold" />


                <EditText
                    android:id="@+id/signPW"
                    android:layout_width="wrap_content"
                    android:layout_height="40dp"
                    android:layout_marginBottom="20dp"
                    android:background="@color/white"
                    android:ems="10"
                    android:hint=" 비밀번호"
                    android:inputType="textPassword"
                    android:text=""
                    android:textColor="@color/black"
                    android:textColorHint="#60000000" />

                <TextView
                    android:id="@+id/signPW2text"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginBottom="5dp"
                    android:text="비밀번호 확인"
                    android:textColor="@color/purple_700"
                    android:textSize="20dp"
                    android:textStyle="bold" />

                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:orientation="horizontal">

                    <EditText
                        android:id="@+id/signPW2"
                        android:layout_width="wrap_content"
                        android:layout_height="40dp"
                        android:layout_marginBottom="20dp"
                        android:background="@color/white"
                        android:ems="10"
                        android:hint=" 비밀번호 다시 입력"
                        android:inputType="textPassword"
                        android:text=""
                        android:textColor="@color/black"
                        android:textColorHint="#60000000" />

                    <androidx.appcompat.widget.AppCompatButton
                        android:id="@+id/pwcheckbutton"
                        android:layout_width="wrap_content"
                        android:layout_height="40dp"
                        android:layout_marginLeft="10dp"
                        android:background="@drawable/cate_button_selector"
                        android:text="확인"
                        android:textColor="@color/white"
                        android:textSize="16sp"
                        android:textStyle="bold" />
                </LinearLayout>

                <androidx.appcompat.widget.AppCompatButton
                    android:id="@+id/signupbutton"
                    android:layout_width="wrap_content"
                    android:layout_height="40dp"
                    android:layout_gravity="center"
                    android:layout_marginTop="20dp"
                    android:background="@drawable/cate_button_selector"
                    android:text="가입"
                    android:textColor="@color/white"
                    android:textSize="16sp"
                    android:textStyle="bold" />

            </LinearLayout>
        </ScrollView>

    </LinearLayout>
</FrameLayout>

 
유저 닉네임과 아이디 비밀번호를 EditText로 유저가 직접 입력할 수 있도록 하였다. 그리고 비밀번호 확인버튼과 뒤로가기 버튼, 회원가입 버튼을 만들어서 누를 수 있게 구현하였다. 
밑은 실제로 구현된 화면이다.
 

 
이 EditText에 있는 값을 가져온 다음 위에 있던 함수와 똑같이 작성해준다음 매개변수만 우리의 값으로 대체해 준다.

String mypw=pw.getText().toString();
String mypw2=pw2.getText().toString();
String myemail=email.getText().toString();
String myname=name.getText().toString();
if(mypw.equals(mypw2)){
    mFirebaseAuth.createUserWithEmailAndPassword(myemail, mypw)
            .addOnCompleteListener(signUp.this, new OnCompleteListener<AuthResult>() {
                @Override
                public void onComplete(@NonNull Task<AuthResult> task) {
                    if (task.isSuccessful()) {
                        // Sign in success, update UI with the signed-in user's information
                        Toast.makeText(signUp.this, "회원가입성공",Toast.LENGTH_LONG).show();
                        FirebaseUser user = mFirebaseAuth.getCurrentUser();
                        UserProfileChangeRequest profileUpdates = new UserProfileChangeRequest.Builder()
                                .setDisplayName(myname).build();
                        user.updateProfile(profileUpdates)
                                .addOnCompleteListener(new OnCompleteListener<Void>() {
                                    @Override
                                    public void onComplete(@NonNull Task<Void> task) {
                                        if (task.isSuccessful()) {

                                        }else{
                                            Log.d(TAG, "실패.");
                                        }
                                    }
                                });

                        onBackPressed();
                    } else {
                        // If sign in fails, display a message to the user.
                        Toast.makeText(signUp.this, "회원가입 실패",Toast.LENGTH_LONG).show();
                    }
                }
            });
}else{
    Toast.makeText(signUp.this, "비밀번호가 중복체크 실패",Toast.LENGTH_LONG).show();
}

2)로그인 

로그인 또한 Firebase 위의 링크의 공식문서에 잘 작성돼있다. 코드를 보자면 

mAuth.signInWithEmailAndPassword(email, password)
        .addOnCompleteListener(this, new OnCompleteListener<AuthResult>() {
            @Override
            public void onComplete(@NonNull Task<AuthResult> task) {
                if (task.isSuccessful()) {
                    // Sign in success, update UI with the signed-in user's information
                    Log.d(TAG, "signInWithEmail:success");
                    FirebaseUser user = mAuth.getCurrentUser();
                    updateUI(user);
                } else {
                    // If sign in fails, display a message to the user.
                    Log.w(TAG, "signInWithEmail:failure", task.getException());
                    Toast.makeText(EmailPasswordActivity.this, "Authentication failed.",
                            Toast.LENGTH_SHORT).show();
                    updateUI(null);
                }
            }
        });
 

 
이 email과 password에 우리의 값을 넣어서 isSuccessful 시에 로그인 성공인 화면이 나올 수 있도록 만들어 보겠다. 
 
일단 기반이 되는 xml 파일을 보자면 회원가입과 동일하게 입력받는 부분이 EditText로 구현되어 있다. 또한 로그인 할 수 있는 버튼과 회원가입화면으로 넘어갈 수 있는 회원가입 글자도 클릭가능하게 만들어 주었다. 

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#AED2FF"
    tools:context=".Login">


    <TextView
        android:id="@+id/loginbackbutton"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:layout_margin="20dp"
        android:layout_weight="1.7"
        android:background="@drawable/backspaceimage"
        android:textColor="@color/purple_700"
        android:textSize="20dp" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginLeft="40dp"
        android:layout_marginTop="200dp"
        android:layout_marginRight="30dp"
        android:layout_marginBottom="30dp"
        android:orientation="vertical">

        <TextView
            android:id="@+id/logintext"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="20dp"
            android:gravity="center_horizontal"
            android:text="회원 로그인"
            android:textAlignment="center"
            android:textColor="@color/purple_700"
            android:textSize="25dp"
            android:textStyle="bold" />

        <EditText
            android:id="@+id/my_Id"
            android:layout_width="wrap_content"
            android:layout_height="40dp"
            android:layout_gravity="center"
            android:layout_marginBottom="10dp"
            android:background="@color/white"
            android:ems="10"
            android:hint="아이디"
            android:inputType="textEmailAddress"
            android:text=""
            android:textColor="@color/black"
            android:textColorHint="@color/gray" />

        <EditText
            android:id="@+id/my_pw"
            android:layout_width="wrap_content"
            android:layout_height="40dp"
            android:layout_gravity="center"
            android:background="@color/white"
            android:ems="10"
            android:hint="비밀번호"
            android:inputType="textPassword"
            android:textColor="@color/black"
            android:textColorHint="@color/gray" />

        <androidx.appcompat.widget.AppCompatButton
            android:id="@+id/loginbutton"
            android:layout_width="wrap_content"
            android:layout_height="35dp"
            android:layout_gravity="center"
            android:layout_marginTop="20dp"
            android:background="@drawable/cate_button_selector"
            android:text="로그인"
            android:textColor="@color/white"
            android:textSize="13sp"
            android:textStyle="bold" />

        <TextView
            android:id="@+id/signin"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:layout_marginTop="10dp"
            android:text="회원가입"
            android:textColor="@color/black"
            android:textSize="13dp"
            android:textStyle="bold" />

    </LinearLayout>
</FrameLayout>

 
실제로 코드로 구현된 화면이다.

 
로그인 버튼을 눌렀을 때 위의 공식문서에서의 코드를 바탕으로 EditText의 값을 받아와서 함수에 넣어주면 된다.

public void onClick(View view) {
    String email=my_Id.getText().toString();
    String password=my_pw.getText().toString();
    mAuth.signInWithEmailAndPassword(email, password)
            .addOnCompleteListener(Login.this, new OnCompleteListener<AuthResult>() {
                @Override
                public void onComplete(@NonNull Task<AuthResult> task) {
                    if (task.isSuccessful()) {
                        Toast.makeText(Login.this,"로그인 성공",Toast.LENGTH_LONG).show();
                        FirebaseUser user = mAuth.getCurrentUser();
                        Intent intent=new Intent(getApplicationContext(),HomeActivity.class);
                        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |  Intent.FLAG_ACTIVITY_CLEAR_TOP);//액티비티 스택제거
                        startActivity(intent);
                    } else {
                        Toast.makeText(Login.this, "로그인 실패", Toast.LENGTH_SHORT).show();
                    }
                }
            });
}

 

3)리뷰 및 좋아요

사실 리뷰 및 좋아요 기능을 구현하려고 할 때 어려웠던 점이 리뷰가 삭제도 가능해야하고 각 관광지의 리뷰와 각 유저간의 리뷰도 구분하여 가져올 수 있어야 하는데 어떻게 구현해야할 지 방식이 생각이 잘 나지않았다. 여러번의 시도 끝에 나는 Firestore에서 문서를 삭제할 때 문서의 ID가 있어야 되기 때문에 리뷰를 저장할 때 생성과 동시에 저장된 리뷰데이터를 불러올 때 문서의 아이디를 저장해주는 방식으로 구현하였고 유저, 관광지 구분을 위해 관광지 이름과 유저의 이메일도 같이 저장하였다. 
 
처음으로 리뷰가 보이게될 틀이 될 xml을 구현해 보았다. 이 리뷰페이지는 관광지 이름을 위에 띄우고 리스트뷰로 리뷰가 리스트형태로 유동적으로 나올 수 있게 만들었다. 그리고 제일 밑에는 리뷰를 작성할 수있게 EditText, 저장할 수 있게 버튼을 붙여 주었다.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_centerHorizontal="false"
    android:background="#FFFFF0"
    android:orientation="vertical"
    android:verticalScrollbarPosition="left">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:orientation="vertical">

        <TextView
            android:id="@+id/cityName"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@color/gray"
            android:gravity="center"
            android:text="관광지 이름"
            android:textColor="#000000"
            android:textSize="34sp"
            android:textStyle="bold" />

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">

            <FrameLayout
                android:id="@+id/routeFragment"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_weight="2">

                <ListView
                    android:id="@+id/review_rlist"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content" />
            </FrameLayout>

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:gravity="center_horizontal"
                android:orientation="horizontal">

                <EditText
                    android:id="@+id/reviewtextbox"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginRight="10dp"
                    android:hint="여기에 리뷰를 작성해 주세요"
                    android:inputType="text"
                    android:padding="15dp"
                    android:textColor="#000000"
                    android:textColorHint="#8B000000" />

                <Button
                    android:id="@+id/reviewbutton"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:scrollIndicators="none"
                    android:text="등록"
                    android:textColor="@color/white" />
            </LinearLayout>

        </LinearLayout>
    </LinearLayout>


</LinearLayout>

 
실제로 구현된 화면이다. 

 
등록버튼을 눌렀을 때 로그인이 돼있는 경우 로그인된 유저의 정보를 Firebaseuser 을 통해 받아온 다음 EditText에 있는 값을 받아오고 유저이메일과 관광지이름을 FireStore에 저장해주면 된다.

user = FirebaseAuth.getInstance().getCurrentUser();
if (user == null) {
    Toast.makeText(getApplicationContext(),"로그인을 하세요",Toast.LENGTH_LONG).show();
    Intent go_intent=new Intent(ReviewActivity.this, Login.class);
    startActivity(go_intent);
} else {
    String email=user.getEmail();
    reviewtextbox=findViewById(R.id.reviewtextbox);
    Map<String, Object> adddata = new HashMap<>();
    adddata.put("review_text", reviewtextbox.getText().toString());
    adddata.put("user_email",email);
    adddata.put("con_name",name);



    db.collection("Review_Data")
            .add(adddata)
            .addOnSuccessListener(new OnSuccessListener<DocumentReference>() {
                @Override
                public void onSuccess(DocumentReference documentReference) {
                    Toast.makeText(getApplicationContext(),"리뷰등록완료!",Toast.LENGTH_LONG).show();
                    review.clear();
                    db.collection("Review_Data")
                            .whereEqualTo("con_name",name)
                            .get()
                            .addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
                                @Override
                                public void onComplete(@NonNull Task<QuerySnapshot> task) {
                                    if (task.isSuccessful()) {
                                        adddata.put("document_id",documentReference.getId());
                                        db.collection("Review_Data").document(documentReference.getId())
                                                .set(adddata)
                                                .addOnSuccessListener(new OnSuccessListener<Void>() {
                                                    @Override
                                                    public void onSuccess(Void aVoid) {

                                                    }
                                                })
                                                .addOnFailureListener(new OnFailureListener() {
                                                    @Override
                                                    public void onFailure(@NonNull Exception e) {
                                                    }
                                                });


                                        for (QueryDocumentSnapshot document : task.getResult()) {
                                            review.add(document.get("review_text",String.class));
                                        }
                                        ReviewListAdapter=new List_Adapter_Review(ReviewActivity.this,review);
                                        review_List.setAdapter(ReviewListAdapter);
                                    } else {
                                        Log.d(TAG, "Error getting documents: ", task.getException());
                                    }
                                }
                            });
                }
            })
            .addOnFailureListener(new OnFailureListener() {
                @Override
                public void onFailure(@NonNull Exception e) {
                    Log.w(TAG, "Error adding document", e);
                }
            });




}

'Project > 경남드리' 카테고리의 다른 글

[Android][JAVA]2장. 경남드리  (0) 2023.10.18
1장. 학기에서 공모전으로  (4) 2023.10.17
0장. 시작  (0) 2023.10.17

처음에 앱을 만들고 Rest API 를 사용하려고 방법을 찾고 공부해봤을 때 어려운 점이 많았어서 내가 배운 것을 정리할 겸 이 글을 보고 Rest API 를 조금 더 쉽게 사용할 수 있도록 이 글을 적어 보려고 한다.
 
일단 첫 글에는 네이버 지도 API 인 Maps에서 Rest API 를 사용하는 법에 대해 예를 들어 적어보려고 한다. 

1) Directions 5 API

https://guide.ncloud-docs.com/docs/maps-direction5-api

 

Directions 5 API

 

guide.ncloud-docs.com

Directions 5는 네이버에서 지원하는 Rest API 중 하나로 출발지 목적지 및 경유지(선택 사항 최대 5개)의 정보를 보내면 응답으로 경로 데이터배열을 받는다. 

1-1)사용법

Rest API의 사용법에서 가장 중요한 것은 요청 파라미터와 헤더를 Interface로 구현하고 응답바디를 Class로 구현 하는 것이다.
Directions 5 공식문서에 보면 

이런 식으로 요청 파라미터, 요청헤더, 요청 바디가 있다.
요청 파라미터와  헤더를 살펴보자

요청 파라미터
요청헤더

보통 헤더는 해당 사이트의 Rest API를 사용하는 경우라면 비슷하거나 같은 경우가 많은 것 같다. 그래서 우리가 중요하게봐야하는 항목은 파라미터 부분이다. 파라미터에 이름들을 전부 공식문서의 이름들과 똑같이 해주어야 한다.  오타나 다르게 하면 작동하지 않는다.. 또한 필수 여부에 Y라고 되어있는 파라미터는 필수적으로 넣어줘야하고 부가적으로 더 얻고 싶은 정보가 있다면 넣어주면 된다. 또한 Rest API는 전부 String 형태로 서버에 요청을 보내기 때문에 파라미터의 자료형은 String으로 맞춰주면 된다. 

public interface RouteFind_nomid {
    @GET("v1/driving")
    Call<RoutePath> getData(@retrofit2.http.Header("X-NCP-APIGW-API-KEY-ID")String ClientID,
                            @Header("X-NCP-APIGW-API-KEY") String Secret,
                            @Query("start") String start, @Query("goal") String goal)
                            ;
}

 
이건 내가 만든 Driving5의 요청 Interface이다. 여기서 Call은 Rest API를 호출할때 사용되는 명으로 나중에 다루기로 한다. 또한 RoutePath는 응답바디를 구성하는 Class 명이다. 그리고 getData라는 메소드를 만들어서 이를 통해 서버에 요청을 보낼 수 있다.
@Get 부분은 서버에 정보를 요청하는 부분인데 @GET("API URL에서 끝에 들어가는부분 ")이다 
예를 들어

https://naveropenapi.apigw.ntruss.com/map-direction/v1/driving

에서에서는 끝부분에 해당하는 v1/dirving 이다
 
●헤더부분은 @Header ("헤더명") 자료형 변수명 으로 구현
● 파라미터 부분은 @Query("파라미터명") 자료형 변수명 으로 구현
 

■요청바디

요청바디 또한 공식문서를 보면 

요청 바디

요청바디는 Class로 구현하는데 필수여부로 돼있는 부분은 필수적으로 다 구현해주어야한다.  또한 이름도 응답바디의 속성명과 똑같이 해주어야 한다. 그리고 변수별로 get메서드를 적어주어야 한다. 타입을 보면 이해하기 어려울 수 있는데 이때는 응답 예시를 보는게 쉬울 수 있다. 예시 응답 또한 공식문서에 첨부되어 있다. 

 
String처럼 자료형이 정해져있는 경우엔 그대로 쓰면 되지만 route 같이 자료형이 이해하기 어려운 경우엔 응답 예시를 보면 route 안에 trafast가 있다. 이 trafast는 위의 요청 파라미터에서 무엇을 요청했는지에 따라 달라지는데 기본값은  traoptimal 이다. 그리고 trafast안에는 summary path 가 있다. 이러한 것을 Class로 구현하려면 Class를 여러개로 만들어야한다.

public class RoutePath {
    @SerializedName("code")
    @Expose
    private int code;
    @SerializedName("messge")
    @Expose
    private String messge;

    @SerializedName("currentDateTime")
    @Expose
    private String currentDateTime;

    @SerializedName("route")
    @Expose
    private Route route;

    public int getCode() {
        return code;
    }

    public Route getRoute() {
        return route;
    }

    public String getCurrentDateTime() {
        return currentDateTime;
    }

    public String getMessge() {
        return messge;
    }

}

class Route{
    @SerializedName("traoptimal")
    @Expose
    private List<Traoptimal> traoptimal;

    public List<Traoptimal> getTraoptimal() {
        return traoptimal;
    }
}

class Traoptimal{
    @SerializedName("summary")
    @Expose
    private Summary summary;
    @SerializedName("path")
    @Expose
    private List<List<Double>> path;

    public Summary getSummary() {
        return summary;
    }

    public List<List<Double>> getPath() {
        return path;
    }
}

class Summary{
    @SerializedName("start")
    @Expose
    private Start start;
    @SerializedName("goal")
    @Expose
    private Goal goal;

    @SerializedName("distance")
    @Expose
    private int distance;

    public Goal getGoal() {
        return goal;
    }

 
이게 예시 응답과 공식문서를 참고하여 만든 Class이다. 보면 Route라는 Class를 만들어서 자료형 처럼 사용하고 있고 그 밑에 여러 Class 및 기본 자료형을 두어서 필수 여부에 Y가 돼있는 것을 다 만들어 주고 Get 메서드까지 달아 주었다. 
이렇게 Interface와 Class를 만들어 주었다면 이제 실제로 Retrofit을 통해 호출해 보자 

■Retrofit
Retrofit은 Rest API를 사용할 때 쓰는 것으로 사용방법은 
1) 의존성 추가 

implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.google.code.gson:gson:2.10.1'
implementation 'com.squareup.retrofit2:converter-gson:2.6.4'

 
gradle에 의존성을 추가해주고 
2) 인터넷 권한 설정

<uses-permission android:name="android.permission.INTERNET" />

Manifests에 권한 설정을 추가해주면 된다. 
 
이제 실제로 호출하는 코드를 보자.

Retrofit retrofit0 = new Retrofit.Builder()
                        .baseUrl("https://naveropenapi.apigw.ntruss.com/map-direction/")
                        .addConverterFactory(GsonConverterFactory.create(gson))
                        .build();
                RouteFind_nomid routeFind0 = retrofit0.create(RouteFind_nomid.class);
                Call<RoutePath> call0 = routeFind0.getData(NavaApIKey,secret,start_lng+","+start_lot,end_lng+","+end_lot);        //네이버 길찾기 rest api 시작 출발점 찍으면 됨
                call0.enqueue(new Callback<RoutePath>() {
                    @Override
                    public void onResponse(Call<RoutePath> call, Response<RoutePath> response) {
                        if(response.isSuccessful()) {
                            RoutePath routePath=response.body();
                            List<List<Double>> path=routePath.getRoute().getTraoptimal().get(0).getPath();
                            PathOverlay line_path=new PathOverlay();       //길 선 표시할 path 배열
                            for(int i=0;i<path.size();i++) {
                                list.add(new LatLng(path.get(i).get(1), path.get(i).get(0)));
                            }
                            markers[0]=new Marker();
                            markers[0].setPosition(new LatLng(start_lot,start_lng));
                            markers[0].setCaptionText("출발지");
                            markers[0].setMap(naverMap);
                            markers[1]=new Marker();
                            markers[1].setPosition(new LatLng(end_lot,end_lng));
                            markers[1].setCaptionText("목적지");
                            markers[1].setMap(naverMap);
                            line_path.setCoords(list);
                            line_path.setMap(naverMap);

                        }
                    }

                    @Override

                    public void onFailure(Call<RoutePath> call, Throwable t) {
                        Log.d(TAG,"실패");
                    }
                });

 
이 코드는 Rest API를 통해 서버에 출발 지 목적지 위경도를 주어서 거기까지의 경로를 선으로 그리고 출발지 목적지에 마커를 찍는 코드이다.

사용할 때 Retrofit 을 선언해주고 .baseUrl 부분에 서버에 요청을 보낼 baseUrl

즉 v1/driving 앞에 부분을 넣어주고 RouteFind_nomid 클래스를 선언해준 Retrofit의 .create 메서드 안에 넣어준다.

그리고 Call 을 선언해서 앞에 선언한 Retrofit이 Interface의 getData의 값을 받게된다.

getData안에 헤더값과 요청하고자 하는 값을 넣어주면 선언 해준 call0로 enque를 보내서 response를 통해 결과값

즉 응답값을 받게 된다.

RoutePath 우리가 선언해준 Class에 response.body() 즉 응답바디를 넣으면 우리가 선언해둔 값들을 사용할 수 있게 되는 것이다. 

결과는 이렇게 잘 나온 것을 볼 수 있다.

경남드리는 경상남도 관광앱으로 사용자가 경상남도의 지역을 출발지, 목적지에 입력하고 테마를 선택하여 경로 만들 수 있게 하는 사용자 맞춤형 여행 설계 앱이다. 
경남드리의 주요 기능에는 여행지 설계 기능, 리뷰기능, 여행지 추천기능이 있다. 이 기능들을 구현하면서 배운 것들에 대해 적어보려고 한다.

1. 여행지 설계기능 

1-1. 홈화면

 처음 홈화면에는 AutoCompleteTextView로 출발지와 도착지를 타이핑할 수 있게 만들었으며, RadioGroup을 활용하여 원하는 테마를 선택할 수 있게 만들었다.
여기서 AutoCompleteTextView에 자동완성이 뜨게 하려면 Adapter 를 붙여 주어야한다 일단 xml에 AutoCompleteTextView를 만든다음

<AutoCompleteTextView
    android:id="@+id/StartPoint"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_margin="5dp"
    android:background="@drawable/border_radius"
    android:ems="10"
    android:hint="출발지"
    android:inputType="text"
    android:padding="5dp"
    android:textColor="#000000"
    android:textColorHint="@color/black" />

<AutoCompleteTextView
    android:id="@+id/EndPoint"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_margin="5dp"
    android:background="@drawable/border_radius"
    android:ems="10"
    android:hint="도착지"
    android:inputType="text"
    android:padding="5dp"
    android:textColor="#000000"
    android:textColorHint="@color/black" />

<Button
    android:id="@+id/MapPoint"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_margin="5dp"
    android:background="#27005D"
    android:text="여행경로만들기"
    android:textColor="@color/white" />

<HorizontalScrollView
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <RadioGroup
        android:id="@+id/rg"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:orientation="horizontal">

        <RadioButton
            android:id="@+id/nature_check"
            android:layout_width="130dp"
            android:layout_height="match_parent"
            android:layout_marginLeft="5dp"
            android:layout_marginRight="10dp"
            android:buttonTint="#000000"
            android:text="자연관광"
            android:textAlignment="center"
            android:textColor="@color/black"
            android:textSize="16sp" />

        <RadioButton
            android:id="@+id/leisure_check"
            android:layout_width="130dp"
            android:layout_height="match_parent"
            android:layout_marginLeft="5dp"
            android:layout_marginRight="10dp"
            android:buttonTint="#000000"
            android:text="레저관광"
            android:textAlignment="center"
            android:textColor="@color/black"
            android:textSize="16sp" />

        <RadioButton
            android:id="@+id/culture_check"
            android:layout_width="130dp"
            android:layout_height="match_parent"
            android:layout_marginLeft="5dp"
            android:layout_marginRight="10dp"
            android:buttonTint="#000000"
            android:text="문화관광"
            android:textAlignment="center"
            android:textColor="@color/black"
            android:textSize="16sp" />
    </RadioGroup>
</HorizontalScrollView>

 
Java코드에서 AutoCompleteTextView를 불러와서 자동완성에서 뜨게할 목록을 ArrayList에 add한 다음 setAdapter로 붙여 주면 된다. 

searchList = new ArrayList<>();
settingList();
StartPoint = findViewById(R.id.StartPoint);
StartPoint.setAdapter(new ArrayAdapter<String>(this, androidx.appcompat.R.layout.support_simple_spinner_dropdown_item, searchList));

EndPoint = findViewById(R.id.EndPoint);
EndPoint.setAdapter(new ArrayAdapter<String>(this, androidx.appcompat.R.layout.support_simple_spinner_dropdown_item, searchList));
private void settingList() {
    searchList.add("창원시");
    searchList.add("진주시");
    searchList.add("통영시");
    searchList.add("사천시");
    searchList.add("김해시");
    searchList.add("밀양시");
    searchList.add("거제시");
    searchList.add("양산시");
    searchList.add("의령군");
    searchList.add("함안군");
    searchList.add("창녕군");
    searchList.add("고성군");
    searchList.add("남해군");
    searchList.add("하동군");
    searchList.add("산청군");
    searchList.add("함양군");
    searchList.add("거창군");
    searchList.add("합천군");
}

여기서는 settingList()라는 함수를 만들어서 searchList에 값을 추가해서 각 출발 목적지 자동완성뷰에 넣어주었다.


1-2.경로선택화면

 출발지와 도착지를 설정하고 버튼을 클릭했을 때 테마별 가중치를 적용한 다 익스트라 알고리즘을 통해 계산된 경유지와 도착지에 대한 관광지의 목록이 나오게 된다. 상단에는 관광지의 테마별 카테고리를 선택할  수 있도록  HorizontalScrollView안에 RadioGroup을 두어 선택할 수 있게 하였다.
 관광지목록이 뜨는 것은 FrameLayout 안에 ListView를 두어 유동적으로 ListView안의 리스트가 변하는 것에 따라 목록이 바뀔 수 있도록했다.

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="#FFFFF0"
    android:orientation="vertical">

    <FrameLayout
        android:id="@+id/routeFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="2">

        <ListView
            android:id="@+id/search_list"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content">

        </ListView>
    </FrameLayout>
</LinearLayout>

1-2-1. 관광지선택
 

처음에는 관광지 선택과 정보보기 둘다 버튼으로 구현하려고 했지만 두개 다 버튼으로 구현했을 때 버튼의 Onclick 이벤트가 작동하지 않아서 관광지 선택은 TextView로 구현하고  리스트뷰를 클릭했을 때 인벤트가 작동하도록 만들어서 선택과 정보보기가 둘다 가능하도록 만들었다. 
또한 선택했을 때 위의 Texview에 경유지가 추가되고 선택해제 했을 때 다시 없어질 수 있도록 구현 하였으면 이때 해당 관광지의 위경도 값도 같이 ArrayList에 추가하여  다음 지도표시화면에 사용할 수 있도록하였다.

int item_n=mid_data.indexOf(item);
double d1=Double.parseDouble(mid_lat.get(item_n));
result_lat.add(d1);
double d2=Double.parseDouble(mid_long.get(item_n));
result_long.add(d2);
Route_List.setOnItemClickListener(new AdapterView.OnItemClickListener() {
    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long l) {
        TextView text_title=view.findViewById(R.id.title);
        TextView route_choice=view.findViewById(R.id.route_choice);

        if(is_Checked.get(position)==false){
            is_Checked.set(position,true);
            route_choice.setBackgroundColor(Color.parseColor("#AED2FF"));
            String item=text_title.getText().toString();
            result_name.add(item);
            if((rot.length-2)>0){
                int item_n=mid_data.indexOf(item);
                double d1=Double.parseDouble(mid_lat.get(item_n));
                result_lat.add(d1);
                double d2=Double.parseDouble(mid_long.get(item_n));
                result_long.add(d2);
            }else {
                int item_n=route_data.indexOf(item);
                double d1=Double.parseDouble(route_lat.get(item_n));
                result_lat.add(d1);
                double d2=Double.parseDouble(route_long.get(item_n));
                result_long.add(d2);
            }

            route_text.append(" "+item);
        }
        else{
            is_Checked.set(position,false);
            route_choice.setBackgroundColor(Color.parseColor("#FFFFF0"));
            String item=text_title.getText().toString();
            result_name.remove(item);
            String remove_item=route_text.getText().toString();
            remove_item=remove_item.replaceAll(item,"");
            route_text.setText(remove_item);
            if((rot.length-2)>0){
                int item_n=mid_data.indexOf(item);
                result_lat.remove(Double.parseDouble(mid_lat.get(item_n)));
                result_long.remove(Double.parseDouble(mid_long.get(item_n)));
            }else {
                int item_n=route_data.indexOf(item);
                result_lat.remove(Double.parseDouble(route_lat.get(item_n)));
                result_long.remove(Double.parseDouble(route_long.get(item_n)));
            }
        }

    }
});

1-2-2. 정보보기 

정보 보기를 클릭했을 때의 관광지 화

관광지 정보보기는 FireStore상에서 그 관광지에 대한 사진, 설명을 쿼리를 작성하여 관광지페이지에 전달해서 관광지화면을 띄울 수 있도록 했다. 
코드가 길어서 일부분만 첨부한다. 

inform_b.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        fileurl.clear();
        data_con.clear();
        String i=title.getText().toString();
        db.collection("nature_data")
                .whereEqualTo("data_title",i)
                .get()
                .addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
                    @Override
                    public void onComplete(@NonNull Task<QuerySnapshot> task) {
                        if (task.isSuccessful()) {

                            for (QueryDocumentSnapshot document : task.getResult()) {
                                fileurl.add(document.get("fileurl1",String.class));
                                data_con.add(document.get("data_content",String.class));
                            }
                            if(fileurl.size()>0&&data_con.size()>0){
                                fileurl1=fileurl.get(0);
                                data_content=data_con.get(0);
                                Intent intent=new Intent(context, City_Page_Activity.class);
                                if(data_content.length()>0 && fileurl1.length()>0 ) {
                                    data_content = data_content.replaceAll("<(/)?([a-zA-Z]*)(\\s[a-zA-Z]*=[^>]*)?(\\s)*(/)?>", "");
                                    data_content = data_content.replaceAll("<[^>]*>", " ");
                                    data_content = data_content.replace("/(<([^>]+)>)/", "");
                                    intent.putExtra("fileurl1", fileurl1);
                                    intent.putExtra("data_content", data_content);
                                    intent.putExtra("name", i);
                                    context.startActivity(intent);
                                }

3. 여행지 길찾기 및 지도 기능

경유지와 출발지 도착지의 위경도 값을 전의 Activity에서 받은 뒤 Rest api로 Naver Map api중 하나인  Driving 5를 호출하여 지도 상에 길선을 그리고 마커를 찍는다. 

-3-1지도기능

●Driving 5
Rest api에 관한 내용을 설명이 길어서 나중에 글을 작성할 예정이지만 여기서 짧게나마 설명하고 넘어가려고 한다.
Rest api를 사용하는데 중요한 2가지 요소는 
해당 서버에 Get요청을 넣을 Interface와 Get 요청을 했을 때 결과값을 받을 Class 이다.
-1 Interface
 Interface는 보통 해당 api 공식문서에 보면 어떻게 적어야하는지 잘 나와있다. 
https://api.ncloud-docs.com/docs/ai-naver-mapsdirections-driving

driving

api.ncloud-docs.com

요청 바디
요청헤더

요청해더는 아래와 같이 @Header를 통해 작성하고 요청바디는 @Query로 작성하고 필수여부에서 Y인 부분을 다 적고 나머지 필요한 것은 추가적으로 적으면 된다.

public interface RouteFind {
    @GET("v1/driving")
    Call<RoutePath> getData(@Header ("X-NCP-APIGW-API-KEY-ID")String ClientID,
                            @Header("X-NCP-APIGW-API-KEY") String Secret,
                            @Query("start") String start,@Query("goal") String goal,
                            @Query("waypoints")String waypoints);
}

-2 Class
 

응답 바디는 필수적인 부분을 Class로 받을 수 있게 공식문서에 나온 타입을 사용하여 작성한다. 또한 get메소드를 작성해서 값을 가져다 쓸 수 있게한다. 

package com.example.k_contest;

import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;

import java.nio.DoubleBuffer;
import java.util.List;

public class RoutePath {
    @SerializedName("code")
    @Expose
    private int code;
    @SerializedName("messge")
    @Expose
    private String messge;

    @SerializedName("currentDateTime")
    @Expose
    private String currentDateTime;

    @SerializedName("route")
    @Expose
    private Route route;

    public int getCode() {
        return code;
    }

    public Route getRoute() {
        return route;
    }

    public String getCurrentDateTime() {
        return currentDateTime;
    }

    public String getMessge() {
        return messge;
    }

}

class Route{
    @SerializedName("traoptimal")
    @Expose
    private List<Traoptimal> traoptimal;

    public List<Traoptimal> getTraoptimal() {
        return traoptimal;
    }
}

class Traoptimal{
    @SerializedName("summary")
    @Expose
    private Summary summary;
    @SerializedName("path")
    @Expose
    private List<List<Double>> path;

    public Summary getSummary() {
        return summary;
    }

    public List<List<Double>> getPath() {
        return path;
    }
}

class Summary{
    @SerializedName("start")
    @Expose
    private Start start;
    @SerializedName("goal")
    @Expose
    private Goal goal;

    @SerializedName("distance")
    @Expose
    private int distance;

    public Goal getGoal() {
        return goal;
    }

    public Start getStart() {
        return start;
    }

    public int getDistance() {
        return distance;
    }
}
class Start
{
    @SerializedName("location")
    @Expose
    private List<Double> location;

    public List<Double> getLocation() {
        return location;
    }
}

class Goal
{
    @SerializedName("location")
    @Expose
    private List<Double> location;

    public List<Double> getLocation() {
        return location;
    }
}

Retrofit retrofit0 = new Retrofit.Builder()
        .baseUrl("https://naveropenapi.apigw.ntruss.com/map-direction/")
        .addConverterFactory(GsonConverterFactory.create(gson))
        .build();
RouteFind_nomid routeFind0 = retrofit0.create(RouteFind_nomid.class);
Call<RoutePath> call0 = routeFind0.getData(NavaApIKey,secret,start_lng+","+start_lot,end_lng+","+end_lot);        //네이버 길찾기 rest api 시작 출발점 찍으면 됨
call0.enqueue(new Callback<RoutePath>() {
    @Override
    public void onResponse(Call<RoutePath> call, Response<RoutePath> response) {
        if(response.isSuccessful()) {
            RoutePath routePath=response.body();
            List<List<Double>> path=routePath.getRoute().getTraoptimal().get(0).getPath();
            PathOverlay line_path=new PathOverlay();       //길 선 표시할 path 배열
            for(int i=0;i<path.size();i++) {
                list.add(new LatLng(path.get(i).get(1), path.get(i).get(0)));
            }
            markers[0]=new Marker();
            markers[0].setPosition(new LatLng(start_lot,start_lng));
            markers[0].setCaptionText("출발지");
            markers[0].setMap(naverMap);
            markers[1]=new Marker();
            markers[1].setPosition(new LatLng(end_lot,end_lng));
            markers[1].setCaptionText("목적지");
            markers[1].setMap(naverMap);
            line_path.setCoords(list);
            line_path.setMap(naverMap);

        }
    }

Rest api를 호출할때는 Retrofit.Builder를 통하여 빌드하고 Get구문과 결과를 받을 Class를 작성하여 값을 받을 수 있게 한다. 또한 결과를 받는 값을 쓸 때는 response.body()를 통해 사용한다. 
도로선을 맵에 사용할때는 path 즉 경로의 수만큼의 ArrrayList를 add하여 사용한다.

-3-2 길찾기 

길찾기는 url Scheme로 구현했는데 Naver api의 url Scheme 문서를 참고하여 구현하면 된다.
https://guide.ncloud-docs.com/docs/maps-url-scheme

지도 앱 연동 URL Scheme

guide.ncloud-docs.com

위의 문서를 참고하여 만약 지도앱이 설치되어있다면 그 앱에 해당하는 url Scheme 기능이 실행되고 아니면 설치화면으로 이동하도록 구현하였다.
 

list = getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
if (list == null || list.isEmpty()) {
    try {
        if(item.equals(st)){
            startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url_f+String.valueOf(region_position[stringToInt(item)][0])+"&dlng="+String.valueOf(region_position[stringToInt(item)][1])+"&dname="+encodeResult+url_b)));
        } else if (item.equals(ed)) {
            startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url_f+String.valueOf(region_position[stringToInt(item)][0])+"&dlng="+String.valueOf(region_position[stringToInt(item)][1])+"&dname="+encodeResult+url_b)));
        }else {
            startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url_f + String.valueOf(result_lat[stringToInt_s(item)]) + "&dlng=" + String.valueOf(result_long[stringToInt_s(item)]) + "&dname=" + encodeResult + url_b)));
        }
    }catch (Exception e){
        startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=com.nhn.android.nmap")));
    }
} else {
    startActivity(intent);
}

'Project > 경남드리' 카테고리의 다른 글

[Android][JAVA]3장. 부가기능  (0) 2023.11.04
1장. 학기에서 공모전으로  (4) 2023.10.17
0장. 시작  (0) 2023.10.17

1. 앱 다시 만들기 

학기가 끝나고 방학 중 처음 개발했던 앱의 기능을 바탕으로 공모전에 나가기 위하여 기존의 팀원 중에 팀장을 비롯하여 개발을 주도적으로 했던 인원과 앱 개발을 해보고자하는 인원을 추가로 모아서 앱의 전반적인 디자인을 고치고  기존에 임의로 값을 적용했던 다 익스트라 알고리즘에서 공공데이터를 활용하여 가중치를 설정하고 이 데이터를 활용하여 앱의 기능 을 추가해보고자 했다. 

개발 초기에는 학기 중에 개발을 해왔던 인원이고 개발을 해보고자 하는 인원이 있어서 위와 같은 문제점은 없었고 개발이 잘 진행되리라 생각을 했다. 

2. 다시 소통의 문제 

개발을 하면서 소통의 부재와 같은 문제점이 다시 생기기 시작하였고 나는 데이터를 활용하기위해 rest api와 같은 방법과  어떻게 데이터를 활용하고 쓸 것인가를 계속 찾아보면서 배우는 것에만 신경을 써서 각자의 업무에서의 현황등 개발과정에서의 애로사항이라던지 도움을 줄 수 있는 부분에 대해 알고자 소통을 하려고 하였지만 내 생각만큼 소통이 이루어지지 않았다.

하지만 나는 이 앱을 꼭 완성하고 싶었고 이 앱의 아이디어와 기능을 구현해낼 수 있다면 충분히 상을 탈 수 있다고 생각 했기에 팀장을 비롯하여 팀원들과 계속 개인적으로든 단체연락에서든 소통을 시도하였고 이 과정에서 어떤 부분이 지금 안되는지 어떤 부분에서 어떻게 하고 있는지 의견을 주고 받으면서 이 앱의 기능을 하나씩 완성해나가려 했다. 

3. 그래도 무사히 제출 

다행히 제출기간안에 앱의 주요 기능들을 완성할 수 있었지만 초기에 계획했던 식당, 축제 표시기능과 esg 지수를 보여주어 선택할 수 있게하는 등 여러기능들을 구현하지 못해서 아쉬웠다. 하지만 처음 만들어보는 앱이고 소재가 괜찮다고 생각해서 공모전에 떨어지더라도 이 앱은 추가기능을 남는 시간에라도 천천히라도 구현을 해서 제대로 된 앱을 만들어보고 싶어졌다. 

 

이번에 앱 개발을 하고 공모전 준비를 해보면서 내가 아직 많이 부족하구나 라는 생각과 내가 모르는 부분이 있다면 검색이나 유투브등 다양한 경로를 통해서 찾다보면 찾을 수 있고 스스로 배워나갈 의지가 제일 중요하구나 라는 생각이 가장 많이 들었다. 앱을 만들든 게임을 만들든 가장 중요한 것은 일단 무엇이든 해보면서 내가 모르는 것이 있더라도 부딪혀보면서 몰랐던 것을 알아가면서 성장하면 되는 것이라고 생각해서 일단 무엇이든 할줄 아는 그런 행동력이다. 비록 소통의 문제라던지 문제를 정확히 파악하고 해결하는 것이 아닌 어영부영 하며 넘어가는 그러한 것으로 많이 부족한 앱을 만들어서 공모전에 내긴 했지만 이 앱을 개발하면서 이러한 기능을 어떻게 개발을 하고 어떤식으로 구현을 하면 되겠다라는 생각을 하고 실제로 코딩을 해보면서 정말 많이 배웠기 때문에 나는 얻은게 분명히 있다고 생각을 한다. 이러한 경험을 바탕으로 더 나은 앱, 게임을 만들어 볼 수  있는 사람이 돼가면 좋겠다. 

 

다음 장에서는 경남드리 앱의 주요 기능을 만들면서 배운 것을 정리해서 올려보려고 한다.

'Project > 경남드리' 카테고리의 다른 글

[Android][JAVA]3장. 부가기능  (0) 2023.11.04
[Android][JAVA]2장. 경남드리  (0) 2023.10.18
0장. 시작  (0) 2023.10.17

● 나의 최종목표는 재미있는 게임을 만드는 것이였지만 직접 개발하여 무엇인가를 만들어 보고 싶었기에 학기 중 프로젝트로 관광앱을 만들어보려고 하였으며 만든 앱을 통하여 경상남도 소프트웨어 공모전에 나가보고자 했다.

그때는 처음으로 협엽을 통한 앱 개발 프로젝트를 해보는 것이라 미숙한 점과 어려웠던 점이 많았는데 크게 두가지 정도 문제점을 살펴보자면 

1. 협업툴 사용의 미숙함 

github와 notion 자주 써보지 않아서 처음에 앱을 빌드하고 협업 기획을 하는 데 초반 2~4주차를 협업툴의 설정과 기획에 써야했다.

2. 소통의 부재 

카톡,디스코드 그리고 매주의 회의를 통해 소통을 통한 빠른 개발을 하려고 하였지만 생각만큼 소통이 원활하게 되지않았다. 

 이러한 문제점에서 협업툴같은 경우는 회의시간에 서로 가르쳐주며 맞춰나가면서 해결을 했는데 소통의 부재는 해결법을 찾기가 어려웠다. 학기 중에 앱 개발을 마쳐야하는 프로젝트였기에 팀장은 아니였지만 팀장과 함께 팀원들과 계속 소통하려고 노력하였으며 안되더라도 나의 개발할 부분을 최대한 빨리 끝내두고 안된 팀원의 부분을 돕거나 코드를 보고 필요하거나 더 개발할 부분을 개발하였다.

  진행이 늦어지면서 처음 생각했던 기능들에서 구현 못한 부분이 많았지만 핵심기능인 길찾기 기능과 다익스트라 알고리즘을 사용한 관광 경유지 추천기능을 완성할 수 있었다. 

 

'Project > 경남드리' 카테고리의 다른 글

[Android][JAVA]3장. 부가기능  (0) 2023.11.04
[Android][JAVA]2장. 경남드리  (0) 2023.10.18
1장. 학기에서 공모전으로  (4) 2023.10.17

■데이터 모델링

테이블간의 관계를 표현하고 설계하는 것

●추상화

현실세계에 있는 데이터를 컴퓨터세계에 데이터베이스로 만들때 필요한 것으로

객체의 속성 중 가장 중요한 것에만 중점을 두어 줄여서 표현하는것이다.

설계 과정

1. 요구사항 수집 및 분석

2. 개념적 모델링

3. 논리적 모델링

4. 물리적 모델링

5. 데이터베이스 구현

 

'Database > 이론' 카테고리의 다른 글

데이터베이스 2강  (0) 2023.04.29
데이터베이스 1강  (0) 2023.04.20

※관계 데이터 모델

■용어 설명

릴레이션 관계 스키마(내포) 인스턴스(외연) 도메인 차수 카디날리티
행과 열로 구성된 테이블 릴레이션과 데이터 , 릴레이션과 릴레이션의 관계 속성,특징  데이터,내용 속성이 가질 수 있는 값 속성의 개수 튜플의 수 

 

 

■릴레이션의 특징

●속성

1.속성은 하나씩만 있어야하며

2.서로 다른 이름,

3.한 속성에서는 같은 도메인값(자료형)

4.순서는 상관없다.

●투플

1.중복은 허용하지 않는다

2.순서는 상관없다

 

 

 

■키

데이터베이스에서 키는 특정 투플을 식별할 때 사용하는 속성 혹은 속성의 집합이다.

●슈퍼키

투플을 유일하게 식별할 수 있는 하나의 속성 혹은 속성의 집합

●후보키 

투플을 유일하게 식별할 수 있는 최소의 집합

●기본키

여러 후보키 중 하나를 선정하여 대표로 삼는 키

※기본키 선정 시 고려사항

1.릴레이션 내 투플을 식별할 수 있는 고유한 값을 가져야함

2.NULL 값은 허용하지 않음

●대리키

마땅한 기본키가 없을 때 일련번호 같은 가상의 속성을 만들어 기본키로 만드는것 

인조키라고 함

●대체키 

기본키로 선정되지않은 후보키

●외래키

관계에 사용, 다른 테이블의 기본키를 참조하는키

※외래키 선정시 고려사항

1.도메인 같아야하고 참조되는 값이 변하면 참조하는 값도 변해야한다.

2.NULL값이 들어가도 되며,중복이 가능하고, 자기자신을 참조할 수 있으며 기본키의 일부도 가능하다.

■데이터무결성

●도메인 무결성 제약조건

투플이 각 속성의 도메인에 지정된 값만을 가져야 한다는 조건

●개체 무결성 제약조건

기본키 제약조건

●참조 무결성 제약조건

자식 릴레이션의 외래키는 부모 릴레이션의 기본키와 도메인이 동일해야 하며, 자식 릴레이션의 값이 변경될 때 부모 릴레이션의 제약을 받는다는 것

->삭제나 수정시 부모 릴레이션과 자식 릴레이션 모두 영향이 있다

■관계대수

어떻게 질의를 수행하는가 -> 절차적인 언어

●관계대수식

연산자<조건> 릴레이

●관계대수 연산자

SELECT δ(시그마) δ<조건>(R) 조건 선택
PROJECT π(파이) π<리스트>(R) 속성 별 리스트 가져오기
JOIN ⋈(보타이) R⋈<조건>S 조건에 맞는 두 릴레이션 가져오기
DIVISION ÷(나누기) R÷S S에 해당하는 값을 가지고 있는 행가져오기

●각 연산 및 연산별 차수/ 카디널리티 변화

  ●합집합

중복제거 후 두 릴레이션을 합침

->연산 후 차수가 같지만 카디널리티는 같거나 작다.

  ●교집합

두 릴레이션이 공통으로 가지고 있는 것을 보여줌

->연산 후 차수는 같고 카디널리티는 두 릴레이션의 어느것 보다 크지않다

  ●차집합

두 릴레이션에서 겹치지않는 부분 보여줌

->연산 후 차수는 같다 

-> 카디널리티 R-S일경우는 R과 같거나 작고 S-R이면 S와 같거나 작다.

  ●카티션프로덕트

->전체x전체 연산

->연산 후 두 릴레이션의 차수를 더한게 차수이고 카디널리티는 두 릴레이션의 카디널리티의 곱이다.

■조인연산

두 릴레이션의 공통 속성을 기준으로 속성 값이 같은 투플을 수평으로 결합하는 연산

R⋈<조건>S

->동등조인 조건에서 =연산자를 사용한 조인 

   ●자연조인

동등조인에서 중복되는 값을 제외한 연산

R⋈N<조건>S

  ●외부조인선이 있는 쪽에 있는값 모두 출력(상대 릴레이션이랑 맞췄을 때 NULL값이여도 출력)

'Database > 이론' 카테고리의 다른 글

데이터베이스3강  (0) 2023.05.01
데이터베이스 1강  (0) 2023.04.20

1. 데이터, 정보, 지식

데이터= 값

정보= 데이터 + 의미

지식-> 정보 + 사물이나 현상에 대한 이해

2. 데이터베이스

데이터베이스란 특정 조직의 여러 사용자가 공유하여 사용할 수 있도록 통합해서 저장한

운영데이터의 집합

=>데이터베이스의 개념

1. 통합된 데이터

2. 저장된 데이터

3. 운영 데이터

4. 공용 데이터

 

■데이터 베이스의 특징

1. 실시간 접근성 

2. 계속적인 변화

3. 동시공유 

4. 내용에 따른 참조 

 

※ 파일 시스템 vs 데이터베이스 시스템

파일 단위로 데이터 관리(word, exel) DBMS로 데이터관리
파일을 직접 다루기 때문에 데이터의 일관성이 훼손될 수 있음 응용프로그램이 DBMS에 파일 접근을 요청

 파일시스템은 데이터가 바뀌면 다시 컴파일해야하고 하나 하나 다 수정해야하는데 이럴 경우 수정이 안된 파일이 있을 수 있다.

DBMS 장점

1. 데이터 중복 최소화

2. 데이터 일관성 유지

3. 데이터 독립성 유지

 

■데이터베이스 구성

데이터 베이스는 스키마로 구성된다.

※스키마 => 개념스키마, 외부스키마, 내부스키마 => 전체적인 구조

※개념스키마

  데이터베이스의 전체적인 구조

●외부스키마

  사용자가 데이터베이스를 사용할 때 상호작용하는 부분

●내부스키마

  데이터가 데이터베이스에 어떻게 저장되는지 

●외부 / 개념 매핑

  데이터가 매핑 될때는 가상 테이블로 매핑된다.

■논리적, 물리적 데이터 독립성

  ●논리적 데이터 독립성

외부스키마 - 개념 스키마 사이의 독립성

개념 스키마가 변경되어도 외부 스키마(응용 프로그램)에는 영향을 미치지 않도록 하는것

  ●물리적 데이터 독립성

개념스키마- 내부 스키마 사이의 독립성

저장장치 구조 변경과 같은 내부 스키마가 변경되어도 개념 스키마에 영향을 미치지 않도록 하는것

'Database > 이론' 카테고리의 다른 글

데이터베이스3강  (0) 2023.05.01
데이터베이스 2강  (0) 2023.04.29

1. 오라클 19c 설치 

https://www.oracle.com/database/technologies/oracle-database-software-downloads.html#19c

2. 압축을 푼 후 setup.exe 실행 

실습할 데이터베이스에 해당하는 아이디 생성후 마침

 

3. sql Developer 설치 

https://www.oracle.com/database/sqldeveloper/technologies/download/

 

Oracle SQL Developer Downloads

This archive. will work on a 32 or 64 bit Windows OS. The bit level of the JDK you install will determine if it runs as a 32 or 64 bit application. This download does not include the required Oracle Java JDK. You will need to install it if it's not already

www.oracle.com

 

1. 깃허브에 파일 올리기

       *git add url

       *git push origin 브랜치이름

       *git remote add origin [원격저장소 주소]

안될경우

       *git push origin +브랜치이름 

       *git reset HEAD add 해둔거 다 삭제

 

 

+ Recent posts