[참고] 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();
}
}
}
끝!!!!!!!!
👇 눈물의 결과물..
'* > Android' 카테고리의 다른 글
[안드로이드] ② Palette (0) | 2022.09.28 |
---|---|
[안드로이드] ① Manifest / 4대 컴포넌트 / Intent (0) | 2022.09.27 |
[안드로이드] Jsoup 사용해서 웹 크롤링 (0) | 2021.10.02 |
[안드로이드] 이미지뷰 크기 조절 (0) | 2021.10.01 |
[안드로이드] Intent Flag 설정하여 로그아웃, 회원탈퇴 처리 (0) | 2021.08.25 |