*/Android

[안드로이드] 다이얼로그 플로우 연동

sssbin 2021. 9. 4. 02:07

[참고] https://www.youtube.com/watch?v=zVxDBBCdpfY 

 

Dialog Flow를 통해 챗봇 학습을 완료시킨 후 안드로이드에 연동하는 방법

리사이클러뷰를 통해 챗봇을 구현한다.

 

✅ 시작하기 전에

- 인터넷 권한 주기 (manifest 파일)

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

- res에 raw 폴더 만들어서 credential.json 파일 넣어주기 (구글 API 인증 정보)

    -> 유출 조심!! 깃에 올릴땐 gitignore 처리해주기

 

1. 우선 리사이클러뷰에 필요한 레이아웃을 만들어준다.

- 액티비티 레이아웃 (전체 화면)

- 아이템 레이아웃 (상대방 대화상자 UI + 내 대화 상자 UI)

(왼) 액티비티 레이아웃 (오) 아이템 레이아웃

 

2. 메시지를 담을 데이터 클래스 만들기

- message: 메시지

- isReceived: 보낸 메시지인지 받은 메시지인지 구분하기 위함

public class MessageData {
    private String message;
    private boolean isReceived;

    public MessageData(String message, boolean isReceived) {
        this.message = message;
        this.isReceived = isReceived;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public boolean getIsReceived() {
        return isReceived;
    }

    public void setIsReceived(boolean isReceived) {
        this.isReceived = isReceived;
    }
}

 

3. 리사이클러뷰 어댑터 만들기

- 리사이클러뷰의 데이터를 관리한다.

- 아이템 바인딩 해주고 

- isReceived == true (메시지를 받았을 때) : 받은 메시지 표시하는 부분만 화면에 보이게 설정해준다.

    -> (내 UI 기준) chat_image, message_receive VISIBLE / message_send GONE

                            message_receive에 setText(message)

- else (메시지를 받지 않았을 때 = 메시지를 보낼 때) : 보낸 메시지 표시하는 부분만 화면에 보이게 설정해준다.

    -> (내 UI 기준) chat_image, message_receive GONE / message_send VISIBLE

                             message_receive에 setText(message)

=> 보내고 받는 메시지를 한번에 같은 UI에 넣어놨기 때문에 위와 같은 설정을 해주지 않으면 대화가 2개씩 반복해서 나타난다!

import android.app.Activity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.List;

import kr.co.company.login.R;
import retrofit.MessageData;

public class ChatAdapter extends RecyclerView.Adapter<ChatAdapter.MyViewHolder> {

    private List<MessageData> messageList;
    private Activity activity;

    public ChatAdapter(List<MessageData> messageList, Activity activity) {
        this.messageList = messageList;
        this.activity = activity;
    }

    @NonNull @Override
    public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(activity).inflate(R.layout.activity_adapter, parent, false);
        return new MyViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
        String message = messageList.get(position).getMessage();
        boolean isReceived = messageList.get(position).getIsReceived();

        if(isReceived){
            holder.chatImage.setVisibility(View.VISIBLE);
            holder.messageReceive.setVisibility(View.VISIBLE);
            holder.messageSend.setVisibility(View.GONE);
            holder.messageReceive.setText(message);
        }else {
            holder.messageSend.setVisibility(View.VISIBLE);
            holder.chatImage.setVisibility(View.GONE);
            holder.messageReceive.setVisibility(View.GONE);
            holder.messageSend.setText(message);
        }
    }

    @Override public int getItemCount() {
        return messageList.size();
    }

    static class MyViewHolder extends RecyclerView.ViewHolder{

        TextView messageSend;
        TextView messageReceive;
        ImageView chatImage;

        MyViewHolder(@NonNull View itemView) {
            super(itemView);
            messageSend = itemView.findViewById(R.id.message_send);
            messageReceive = itemView.findViewById(R.id.message_receive);
            chatImage = itemView.findViewById(R.id.chatImage);
        }
    }

}

 

4. BotReply 인터페이스

- callback 메서드 -> 요청이 성공적인지 확인하는 역할을 할 것임!

import com.google.cloud.dialogflow.v2.DetectIntentResponse;

public interface BotReply {
    void callback(DetectIntentResponse returnResponse);
}

 

5. SendMessageInBg

- request 값을 담아 DialogFlow로 보내고 response를 받아 처리하는 역할을 함!!

- AsyncTask 참고 -> https://junyoung-developer.tistory.com/119 (Deprecated 됐다고 함···)

- AsyncTask 대체 -> https://velog.io/@plz_no_anr/Android-AsyncTask를-RxJava로-대체하기(아직 내 수준에서는 이해하기 힘드니 일단 넘어가자....)

import android.os.AsyncTask;
import android.util.Log;

import com.google.cloud.dialogflow.v2.DetectIntentRequest;
import com.google.cloud.dialogflow.v2.DetectIntentResponse;
import com.google.cloud.dialogflow.v2.QueryInput;
import com.google.cloud.dialogflow.v2.SessionName;
import com.google.cloud.dialogflow.v2.SessionsClient;
import kr.co.company.login.interfaces.BotReply;

public class SendMessageInBg extends AsyncTask<Void, Void, DetectIntentResponse>{
    private SessionName session;
    private SessionsClient sessionsClient;
    private QueryInput queryInput;
    private String TAG = "async";
    private BotReply botReply;

    public SendMessageInBg(BotReply botReply, SessionName session, SessionsClient sessionsClient,
                           QueryInput queryInput) {
        this.botReply = botReply;
        this.session = session;
        this.sessionsClient = sessionsClient;
        this.queryInput = queryInput;
    }

    @Override
    protected DetectIntentResponse doInBackground(Void... voids) {
        try {
            //DetectIntentRequest를 통해 request 값을 담아서
            DetectIntentRequest detectIntentRequest =
                    DetectIntentRequest.newBuilder()
                            .setSession(session.toString())
                            .setQueryInput(queryInput)
                            .build();
            //sessionsClient의 detectIntent 메서드에 담아 호출 -> DetectIntentResponse 객체를 반환함.
            return sessionsClient.detectIntent(detectIntentRequest);
        } catch (Exception e) {
            Log.d(TAG, "doInBackground: " + e.getMessage());
            e.printStackTrace();
        }
        return null;
    }

    @Override
    protected void onPostExecute(DetectIntentResponse response) {
        //handle return response here
        botReply.callback(response);
    }
}

 

6. 챗봇을 구현할 메인액티비티

- BotReply를 implements

- 어댑터, 데이터 설정

- 메시지에 챗봇 초기 메시지를 넣어준다. (다이얼로그 플로우에서 제공하지 않기 때문에 안드로이드에서 수동으로 넣어줬음)

- setUpBot (다이얼로그 플로우 세팅해주기)

- 메시지를 보내면

   -> 데이터 리스트에 메시지를 담고

   -> sendMessageToBot (쿼리에 메시지를 담아서 SendMsgInBg 호출)

   -> callBack (요청이 성공적인지 확인 후 받은 메시지를 데이터 리스트에 담음)

   -> 어댑터 데이터가 변경되었으니 새로고침!

import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.Toast;

import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.RecyclerView;

import com.google.api.gax.core.FixedCredentialsProvider;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.auth.oauth2.ServiceAccountCredentials;
import com.google.cloud.dialogflow.v2.DetectIntentResponse;
import com.google.cloud.dialogflow.v2.QueryInput;
import com.google.cloud.dialogflow.v2.SessionName;
import com.google.cloud.dialogflow.v2.SessionsClient;
import com.google.cloud.dialogflow.v2.SessionsSettings;
import com.google.cloud.dialogflow.v2.TextInput;
import com.google.common.collect.Lists;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.UUID;

import kr.co.company.login.adapters.ChatAdapter;
import kr.co.company.login.helpers.SendMessageInBg;
import kr.co.company.login.interfaces.BotReply;
import retrofit.MessageData;

public class ChatActivity extends AppCompatActivity implements BotReply {
    RecyclerView chatView;
    ChatAdapter chatAdapter;
    List<MessageData> messageList = new ArrayList<>();
    EditText editMessage;
    ImageButton btnSend;
    Button backButton;
    ImageButton helpbtn;
    
    private SessionsClient sessionsClient;
    private SessionName sessionName;
    private String uuid = UUID.randomUUID().toString();
    private String TAG = "chatactivity";

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_chat);

        chatView = findViewById(R.id.chatView);
        editMessage = findViewById(R.id.editMessage);
        btnSend = findViewById(R.id.btnSend);
        backButton = findViewById(R.id.backButton);
        helpbtn = findViewById(R.id.helpButton);


        chatAdapter = new ChatAdapter(messageList, this);
        chatView.setAdapter(chatAdapter);

        messageList.add(new MessageData("안녕하세요. 저는 인공지능 챗봇입니다.\n 신청할 민원 내용을 직접 입력해주세요.\n( ex. 신고, 조회 )", true));

        btnSend.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                String message = editMessage.getText().toString();
                System.out.println(message);
                if (!message.isEmpty()) {
                    messageList.add(new MessageData(message, false));
                    editMessage.setText("");
                    sendMessageToBot(message);
                    Objects.requireNonNull(chatView.getAdapter()).notifyDataSetChanged();
                    Objects.requireNonNull(chatView.getLayoutManager())
                            .scrollToPosition(messageList.size() - 1);
                } else {
                    Toast.makeText(ChatActivity.this, "Please enter text!", Toast.LENGTH_SHORT).show();
                }
            }
        });

        setUpBot();
    }

    private void setUpBot() {
        try {
            InputStream stream = this.getResources().openRawResource(R.raw.credential);
            GoogleCredentials credentials = GoogleCredentials.fromStream(stream)
                    .createScoped(Lists.newArrayList("https://www.googleapis.com/auth/cloud-platform"));
            String projectId = ((ServiceAccountCredentials) credentials).getProjectId();

            SessionsSettings.Builder settingsBuilder = SessionsSettings.newBuilder();
            SessionsSettings sessionsSettings = settingsBuilder.setCredentialsProvider(
                    FixedCredentialsProvider.create(credentials)).build();
            sessionsClient = SessionsClient.create(sessionsSettings);
            sessionName = SessionName.of(projectId, uuid);

            Log.d(TAG, "projectId : " + projectId);
        } catch (Exception e) {
            Log.d(TAG, "setUpBot: " + e.getMessage());
        }
    }

    private void sendMessageToBot(String message) {
        QueryInput input = QueryInput.newBuilder()
                .setText(TextInput.newBuilder().setText(message).setLanguageCode("ko-US")).build();
        new SendMessageInBg(this, sessionName, sessionsClient, input).execute();
    }

    @Override
    public void callback(DetectIntentResponse returnResponse) {
        if (returnResponse != null) {
            String botReply = returnResponse.getQueryResult().getFulfillmentText();
            if (!botReply.isEmpty()) {
                messageList.add(new MessageData(botReply, true));
                chatAdapter.notifyDataSetChanged();
                Objects.requireNonNull(chatView.getLayoutManager()).scrollToPosition(messageList.size() - 1);
            } else {
                Toast.makeText(this, "something went wrong", Toast.LENGTH_SHORT).show();
            }
        } else {
            Toast.makeText(this, "failed to connect!", Toast.LENGTH_SHORT).show();
        }
    }

}


끝!!!!!!!!

 

👇 눈물의 결과물..

(1,2) UI 적용 전 (3) UI 적용 후