본문 바로가기
안드로이드 웹앱 콘테츠 개발자 양성(국비지원)/Firebase

Android Studio(기능) Firebase - Database+Storage [채팅창 만들기3]

by 차누감 2019. 10. 24.

Android Studio(기능)  Firebase - Database+Storage [채팅창 만들기2] 에서 첫 화면에 접속하고 Fire Database, Storage에 저장하였다. 그리고 앱을 종료하고 다시 실행시키면 기존에 썼던 데이터가 자동으로 입력된다.

 

이제 두 번째 화면(채팅화면) ChatActivity을 구현하겠다.

 

<최종 실행 화면>

기존에 했던 대화들도 보이고 상대와 나의 말이 구분된다.

이앱을 다른 기기에서 실행하면 같이 대화할 수 있다!!! (신기하다.. :-)


 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:orientation="vertical"
    tools:context=".ChatActivity">
    <ListView
        android:id="@+id/listview"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:divider="#ffffff"
        android:dividerHeight="0dp"/>
 
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:padding="4dp"
        android:background="@color/colorPrimary"
        android:gravity="center_vertical">
        <EditText
            android:id="@+id/et"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:hint="Input message"
            android:inputType="textMultiLine"
            android:padding="10dp"
            android:maxLines="3"
            android:background="@drawable/back_et_name"/>
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="send"
            android:backgroundTint="#ff8800"
            android:layout_marginLeft="8dp"
            android:onClick="clickSend"/>
    </LinearLayout>
</LinearLayout>
 
 
 

채팅창에 글 마다에 사진, 이름, 시간, 메세지 내용이 있다.

이것을 class로 새로 만들어서 데이터를 저장한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
 
public class MessageItem {
 
    String name;
    String message;
    String time;
    String pofileUrl;
 
    public MessageItem(String name, String message, String time, String pofileUrl) {
        this.name = name;
        this.message = message;
        this.time = time;
        this.pofileUrl = pofileUrl;
    }
 
    //firebase DB에 객체로 값을 읽어올 때..
    //파라미터가 비어있는 생성자가 핑요함.
    public MessageItem() {
    }
 
    //Getter & Setter
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public String getMessage() {
        return message;
    }
 
    public void setMessage(String message) {
        this.message = message;
    }
 
    public String getTime() {
        return time;
    }
 
    public void setTime(String time) {
        this.time = time;
    }
 
    public String getPofileUrl() {
        return pofileUrl;
    }
 
    public void setPofileUrl(String pofileUrl) {
        this.pofileUrl = pofileUrl;
    }
}
 
 
 

아이템 하나의 모양을 만들자. (두개의 모양 필요 : 내 메세지 박스, 상대방 메세지 박스  )

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="16dp">
 
    <de.hdodenhof.circleimageview.CircleImageView
        android:id="@+id/iv"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:src="@mipmap/ic_launcher"
        android:layout_alignParentRight="true"/>
    <TextView
        android:id="@+id/tv_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="name"
        android:textColor="#333333"
        android:layout_toLeftOf="@+id/iv"
        android:layout_marginRight="16dp"/>
 
    <TextView
        android:id="@+id/tv_msg"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello android"
        android:layout_below="@+id/tv_name"
        android:layout_toLeftOf="@id/iv"
        android:layout_marginRight="16dp"
        android:padding="12dp"
        android:maxWidth="250dp"
        android:background="@drawable/back_et_mymsgbox"
        android:textColor="#ffffff"/>
    <TextView
        android:id="@+id/tv_time"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="13:35"
        android:textSize="12sp"
        android:layout_toLeftOf="@+id/tv_msg"
        android:layout_marginRight="8dp"
        android:layout_alignBottom="@id/tv_msg"/>
</RelativeLayout>
 
 
 

내 메세지 박스 모양을 만들자.

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="utf-8"?>
    android:shape="rectangle">
 
    <solid android:color="@color/colorPrimary"/>
    <stroke android:color="@color/colorPrimaryDark" android:width="1dp"/>
    <corners android:radius="4dp"/>
 
</shape>
 
 
 

만든 모양을 적용했다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="16dp">
 
    <de.hdodenhof.circleimageview.CircleImageView
        android:id="@+id/iv"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:src="@mipmap/ic_launcher"
        android:layout_alignParentRight="true"/>
    <TextView
        android:id="@+id/tv_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="name"
        android:textColor="#333333"
        android:layout_toLeftOf="@+id/iv"
        android:layout_marginRight="16dp"/>
 
    <TextView
        android:id="@+id/tv_msg"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello android"
        android:layout_below="@+id/tv_name"
        android:layout_toLeftOf="@id/iv"
        android:layout_marginRight="16dp"
        android:padding="12dp"
        android:maxWidth="250dp"
        android:background="@drawable/back_et_mymsgbox"
        android:textColor="#ffffff"/>
    <TextView
        android:id="@+id/tv_time"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="13:35"
        android:textSize="12sp"
        android:layout_toLeftOf="@+id/tv_msg"
        android:layout_marginRight="8dp"
        android:layout_alignBottom="@id/tv_msg"/>
</RelativeLayout>
 
 
 

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="utf-8"?>
    android:shape="rectangle">
 
    <solid android:color="#ffffff"/>
    <stroke android:color="@color/colorPrimaryDark" android:width="1dp"/>
    <corners android:radius="4dp"/>
 
</shape>
 
 
 

기존에 Left를 Right로 Right를 Left로 바꾸고 circle에 aligneparent를 없애야 이미지가 오른쪽에 있을 것이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="16dp">
 
    <de.hdodenhof.circleimageview.CircleImageView
        android:id="@+id/iv"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:src="@mipmap/ic_launcher"
        />
    <TextView
        android:id="@+id/tv_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="name"
        android:textColor="#333333"
        android:layout_toRightOf="@+id/iv"
        android:layout_marginLeft="16dp"/>
 
    <TextView
        android:id="@+id/tv_msg"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello android"
        android:layout_below="@+id/tv_name"
        android:layout_toRightOf="@id/iv"
        android:layout_marginLeft="16dp"
        android:padding="12dp"
        android:maxWidth="250dp"
        android:background="@drawable/back_et_othermsgbox"
        android:textColor="#333333"/>
    <TextView
        android:id="@+id/tv_time"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="13:35"
        android:textSize="12sp"
        android:layout_toRightOf="@+id/tv_msg"
        android:layout_marginLeft="8dp"
        android:layout_alignBottom="@id/tv_msg"/>
</RelativeLayout>
 
 
 

이제 정보를 화면에 보여줄 Adapter를 만들자.

MainActivity로 가서 ListView와 Adapter 연결

이제 Send 버튼 기능을 작성하자.

ChatActivity.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
 
 
import android.content.Context;
 
 
 
public class ChatActivity extends AppCompatActivity {
 
    EditText et;
    ListView listView;
 
    ArrayList<MessageItem> messageItems=new ArrayList<>();
    ChatAdapter adapter;
 
    //Firebase Database 관리 객체참조변수
    FirebaseDatabase firebaseDatabase;
 
    //'chat'노드의 참조객체 참조변수
    DatabaseReference chatRef;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_chat);
 
        //제목줄 제목글시를 닉네임으로(또는 채팅방)
        getSupportActionBar().setTitle(G.nickName);
 
        et=findViewById(R.id.et);
        listView=findViewById(R.id.listview);
        adapter=new ChatAdapter(messageItems,getLayoutInflater());
        listView.setAdapter(adapter);
 
        //Firebase DB관리 객체와 'caht'노드 참조객체 얻어오기
        firebaseDatabase= FirebaseDatabase.getInstance();
        chatRef= firebaseDatabase.getReference("chat");
 
 
        //firebaseDB에서 채팅 메세지들 실시간 읽어오기..
 
    }
 
    public void clickSend(View view) {
 
        //firebase DB에 저장할 값들( 닉네임, 메세지, 프로필 이미지URL, 시간)
        String nickName= G.nickName;
        String message= et.getText().toString();
        String pofileUrl= G.porfileUrl;
 
        //메세지 작성 시간 문자열로..
        Calendar calendar= Calendar.getInstance(); //현재 시간을 가지고 있는 객체
 
        //firebase DB에 저장할 값(MessageItem객체) 설정
        MessageItem messageItem= new MessageItem(nickName,message,time,pofileUrl);
        //'char'노드에 MessageItem객체를 통해
 
        //EditText에 있는 글씨 지우기
        et.setText("");
 
        //소프트키패드를 안보이도록..
        InputMethodManager imm=(InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
        imm.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(),0);
 
        //처음 시작할때 EditText가 다른 뷰들보다 우선시 되어 포커스를 받아 버림.
        //즉, 시작부터 소프트 키패드가 올라와 있음.
 
        //그게 싫으면...다른 뷰가 포커스를 가지도록
        //즉, EditText를 감싼 Layout에게 포커스를 가지도록 속성을 추가!![[XML에]
    }
}
 
 
 

시작부터 키패드를 안띄우려면 Edit View를 감싸고 있는 LinearLayout에 focus를 줘야한다.

이제 채팅창에 쓴 메세지를 띄워보자.

ChatActivity,java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
 
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
 
import android.content.Context;
 
 
 
public class ChatActivity extends AppCompatActivity {
 
    EditText et;
    ListView listView;
 
    ArrayList<MessageItem> messageItems=new ArrayList<>();
    ChatAdapter adapter;
 
    //Firebase Database 관리 객체참조변수
    FirebaseDatabase firebaseDatabase;
 
    //'chat'노드의 참조객체 참조변수
    DatabaseReference chatRef;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_chat);
 
        //제목줄 제목글시를 닉네임으로(또는 채팅방)
        getSupportActionBar().setTitle(G.nickName);
 
        et=findViewById(R.id.et);
        listView=findViewById(R.id.listview);
        adapter=new ChatAdapter(messageItems,getLayoutInflater());
        listView.setAdapter(adapter);
 
        //Firebase DB관리 객체와 'caht'노드 참조객체 얻어오기
        firebaseDatabase= FirebaseDatabase.getInstance();
        chatRef= firebaseDatabase.getReference("chat");
 
 
        //firebaseDB에서 채팅 메세지들 실시간 읽어오기..
        //'chat'노드에 저장되어 있는 데이터들을 읽어오기
        //chatRef에 데이터가 변경되는 것으 듣는 리스너 추가
        chatRef.addChildEventListener(new ChildEventListener() {
            //새로 추가된 것만 줌 ValueListener는 하나의 값만 바뀌어도 처음부터 다시 값을 줌
            @Override
            public void onChildAdded(@NonNull DataSnapshot dataSnapshot, @Nullable String s) {
 
                //새로 추가된 데이터(값 : MessageItem객체) 가져오기
                MessageItem messageItem= dataSnapshot.getValue(MessageItem.class);
 
                //새로운 메세지를 리스뷰에 추가하기 위해 ArrayList에 추가
                messageItems.add(messageItem);
 
                //리스트뷰를 갱신
                adapter.notifyDataSetChanged();
                listView.setSelection(messageItems.size()-1); //리스트뷰의 마지막 위치로 스크롤 위치 이동
            }
 
            @Override
            public void onChildChanged(@NonNull DataSnapshot dataSnapshot, @Nullable String s) {
 
            }
 
            @Override
            public void onChildRemoved(@NonNull DataSnapshot dataSnapshot) {
 
            }
 
            @Override
            public void onChildMoved(@NonNull DataSnapshot dataSnapshot, @Nullable String s) {
 
            }
 
            @Override
            public void onCancelled(@NonNull DatabaseError databaseError) {
 
            }
        });
 
    }
 
    public void clickSend(View view) {
 
        //firebase DB에 저장할 값들( 닉네임, 메세지, 프로필 이미지URL, 시간)
        String nickName= G.nickName;
        String message= et.getText().toString();
        String pofileUrl= G.porfileUrl;
 
        //메세지 작성 시간 문자열로..
        Calendar calendar= Calendar.getInstance(); //현재 시간을 가지고 있는 객체
 
        //firebase DB에 저장할 값(MessageItem객체) 설정
        MessageItem messageItem= new MessageItem(nickName,message,time,pofileUrl);
        //'char'노드에 MessageItem객체를 통해
 
        //EditText에 있는 글씨 지우기
        et.setText("");
 
        //소프트키패드를 안보이도록..
        InputMethodManager imm=(InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
        imm.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(),0);
 
        //처음 시작할때 EditText가 다른 뷰들보다 우선시 되어 포커스를 받아 버림.
        //즉, 시작부터 소프트 키패드가 올라와 있음.
 
        //그게 싫으면...다른 뷰가 포커스를 가지도록
        //즉, EditText를 감싼 Layout에게 포커스를 가지도록 속성을 추가!![[XML에]
    }
}
 
 
 

<최종 실행 화면>

기존에 했던 대화들도 보이고 상대와 나의 말이 구분된다.

이앱을 다른 기기에서 실행하면 같이 대화할 수 있다!!! (신기)


복붙용 코드

@layout

activity_main.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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"
    tools:context=".MainActivity">
 
    <EditText
        android:id="@+id/et_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:ems="10"
        android:maxLength="10"
        android:layout_centerInParent="true"
        android:hint="Input nick name"
        android:inputType="text"
        android:gravity="center"
        android:padding="8dp"
        android:background="@drawable/back_et_name"
        android:textCursorDrawable="@drawable/et_name_cursor"/>
<!--    ems최소 글자로-->
<!--    android:textCursorDrawable="@null" 이면 글씨 색과 같은 커서색-->
 
    <de.hdodenhof.circleimageview.CircleImageView
        android:id="@+id/iv_profile"
        android:layout_width="80dp"
        android:layout_height="80dp"
        android:layout_centerInParent="true"
        android:layout_above="@id/et_name"
        android:layout_marginBottom="16dp"
        android:src="@mipmap/ic_launcher"
        android:clickable="true"
        android:onClick="clickImage"/>
 
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="입장"
        android:layout_alignParentRight="true"
        android:layout_alignParentBottom="true"
        android:layout_margin="16dp"
        android:onClick="clickBtn"/>
</RelativeLayout>
 
 
 

activity_char.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:orientation="vertical"
    tools:context=".ChatActivity">
    <ListView
        android:id="@+id/listview"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:divider="#ffffff"
        android:dividerHeight="0dp"/>
 
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:padding="4dp"
        android:background="@color/colorPrimary"
        android:gravity="center_vertical"
        android:focusable="true"
        android:focusableInTouchMode="true">
        <EditText
            android:id="@+id/et"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:hint="Input message"
            android:inputType="textMultiLine"
            android:padding="10dp"
            android:maxLines="3"
            android:background="@drawable/back_et_name"/>
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="send"
            android:backgroundTint="#ff8800"
            android:layout_marginLeft="8dp"
            android:onClick="clickSend"/>
    </LinearLayout>
</LinearLayout>
 
 
 

my_msgbox.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="16dp">
 
    <de.hdodenhof.circleimageview.CircleImageView
        android:id="@+id/iv"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:src="@mipmap/ic_launcher"
        android:layout_alignParentRight="true"/>
    <TextView
        android:id="@+id/tv_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="name"
        android:textColor="#333333"
        android:layout_toLeftOf="@+id/iv"
        android:layout_marginRight="16dp"/>
 
    <TextView
        android:id="@+id/tv_msg"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello android"
        android:layout_below="@+id/tv_name"
        android:layout_toLeftOf="@id/iv"
        android:layout_marginRight="16dp"
        android:padding="12dp"
        android:maxWidth="250dp"
        android:background="@drawable/back_et_mymsgbox"
        android:textColor="#ffffff"/>
    <TextView
        android:id="@+id/tv_time"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="13:35"
        android:textSize="12sp"
        android:layout_toLeftOf="@+id/tv_msg"
        android:layout_marginRight="8dp"
        android:layout_alignBottom="@id/tv_msg"/>
</RelativeLayout>
 
 
 

other_msgbox.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="16dp">
 
    <de.hdodenhof.circleimageview.CircleImageView
        android:id="@+id/iv"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:src="@mipmap/ic_launcher"
        />
    <TextView
        android:id="@+id/tv_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="name"
        android:textColor="#333333"
        android:layout_toRightOf="@+id/iv"
        android:layout_marginLeft="16dp"/>
 
    <TextView
        android:id="@+id/tv_msg"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello android"
        android:layout_below="@+id/tv_name"
        android:layout_toRightOf="@id/iv"
        android:layout_marginLeft="16dp"
        android:padding="12dp"
        android:maxWidth="250dp"
        android:background="@drawable/back_et_othermsgbox"
        android:textColor="#333333"/>
    <TextView
        android:id="@+id/tv_time"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="13:35"
        android:textSize="12sp"
        android:layout_toRightOf="@+id/tv_msg"
        android:layout_marginLeft="8dp"
        android:layout_alignBottom="@id/tv_msg"/>
</RelativeLayout>
 
 
 

 


@drawable

 

back_et_name.xml

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="utf-8"?>
    android:shape="rectangle">
 
    <solid android:color="#ffffff"/>
    <stroke android:color="@color/colorPrimary" android:width="2dp"/>
    <corners android:radius="4dp"/>
 
</shape>
 
 

back_et_mymsgbox.xml

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="utf-8"?>
    android:shape="rectangle">
 
    <solid android:color="@color/colorPrimary"/>
    <stroke android:color="@color/colorPrimaryDark" android:width="1dp"/>
    <corners android:radius="4dp"/>
 
</shape>
 
 
 

back_et_othermsgbox.xml

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="utf-8"?>
    android:shape="rectangle">
 
    <solid android:color="#ffffff"/>
    <stroke android:color="@color/colorPrimaryDark" android:width="1dp"/>
    <corners android:radius="4dp"/>
 
</shape>
 
 
 

et_name_cursor.xml

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="utf-8"?>
    android:shape="rectangle">
 
    <solid android:color="@color/colorPrimary"/>
    <size android:width="2dp"/>
 
 
</shape>
 
 

@java

MainActivity.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
 
import androidx.annotation.Nullable;
 
import android.content.SharedPreferences;
 
import com.squareup.picasso.Picasso;
 
 
import de.hdodenhof.circleimageview.CircleImageView;
 
public class MainActivity extends AppCompatActivity {
    EditText etName;
    CircleImageView ivProfile;
 
    Uri imgUri;//선택한 프로필 이미지 경로 Uri
 
    boolean isFirst= true//앱을 처음 실행한 것인가?
    boolean isChanged= false//프로필을 변경한 적이 있는가?
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 
        etName=findViewById(R.id.et_name);
        ivProfile=findViewById(R.id.iv_profile);
 
        //폰에 저장되어 있는 프로필 읽어오기
        loadData();
        if(G.nickName!=null){
            etName.setText(G.nickName);
 
            //처음이 아니다, 즉, 이미 접속한 적이 있다.
            isFirst=false;
 
        }
    }
 
    public void clickImage(View view) {
        //프로필 이미지 선택하도록 Gallery 앱 실행
        Intent intent= new Intent(Intent.ACTION_PICK);
        intent.setType("image/*");
        startActivityForResult(intent,10);
    }
    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        switch (requestCode){
            case 10:
                if(resultCode==RESULT_OK){
                    imgUri= data.getData();
                    //Glide.with(this).load(imgUri).into(ivProfile);
                    //Glide는 이미지를 읽어와서 보여줄때 내 device의 외장메모리에 접근하는 퍼미션이 요구됨.
                    //(퍼미션이 없으면 이미지가 보이지 않음.)
                    //Glide를 사용할 때는 동적 퍼미션 필요함.
 
                    //Picasso 라이브러리는 퍼미션 없어도 됨.
                    Picasso.get().load(imgUri).into(ivProfile);
 
                    //변경된 이미지가 있다.
                    isChanged=true;
                }
                break;
        }
    }
    public void clickBtn(View view) {
 
        //바꾼것도 없고, 처음 접속도 아니고..
        if(!isChanged && !isFirst){
            //ChatActivity로 전환
            Intent intent= new Intent(this, ChatActivity.class);
            startActivity(intent);
            finish();
        }else{
            //1. save작업
            saveData();
        }
    }
 
    void saveData(){
        //EditText의 닉네임 가져오기 [전역변수에]
        G.nickName= etName.getText().toString();
 
        //이미지를 선택하지 않았을 수도 있으므로
        if(imgUri==nullreturn;
 
        //Firebase storage에 이미지 저장하기 위해 파일명 만들기(날짜를 기반으로)
        SimpleDateFormat sdf= new SimpleDateFormat("yyyMMddhhmmss"); //20191024111224
        String fileName= sdf.format(new Date())+".png";
 
        //Firebase storage에 저장하기
        FirebaseStorage firebaseStorage= FirebaseStorage.getInstance();
        final StorageReference imgRef= firebaseStorage.getReference("profileImages/"+fileName);
 
        //파일 업로드
        UploadTask uploadTask=imgRef.putFile(imgUri);
        uploadTask.addOnSuccessListener(new OnSuccessListener<UploadTask.TaskSnapshot>() {
            @Override
            public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) {
                //이미지 업로드가 성공되었으므로...
                //곧바로 firebase storage의 이미지 파일 다운로드 URL을 얻어오기
                imgRef.getDownloadUrl().addOnSuccessListener(new OnSuccessListener<Uri>() {
                    @Override
                    public void onSuccess(Uri uri) {
                        //파라미터로 firebase의 저장소에 저장되어 있는
                        //이미지에 대한 다운로드 주소(URL)을 문자열로 얻어오기
                        G.porfileUrl= uri.toString();
                        Toast.makeText(MainActivity.this"프로필 저장 완료", Toast.LENGTH_SHORT).show();
 
                        //1. Firebase Database에 nickName, profileUrl을 저장
                        //firebase DB관리자 객체 소환
                        FirebaseDatabase firebaseDatabase=FirebaseDatabase.getInstance();
                        //'profiles'라는 이름의 자식 노드 참조 객체 얻어오기
                        DatabaseReference profileRef= firebaseDatabase.getReference("profiles");
 
                        //닉네임을 key 식별자로 하고 프로필 이미지의 주소를 값으로 저장
                        profileRef.child(G.nickName).setValue(G.porfileUrl);
 
                        //2. 내 phone에 nickName, profileUrl을 저장
                        SharedPreferences preferences= getSharedPreferences("account",MODE_PRIVATE);
                        SharedPreferences.Editor editor=preferences.edit();
 
                        editor.putString("nickName",G.nickName);
                        editor.putString("profileUrl", G.porfileUrl);
 
                        editor.commit();
                        //저장이 완료되었으니 ChatActivity로 전환
                        Intent intent=new Intent(MainActivity.this, ChatActivity.class);
                        startActivity(intent);
                        finish();
 
                    }
                });
            }
        });
    }//saveData() ..
 
    //내 phone에 저장되어 있는 프로필정보 읽어오기
    void loadData(){
        SharedPreferences preferences=getSharedPreferences("account",MODE_PRIVATE);
        G.nickName=preferences.getString("nickName"null);
        G.porfileUrl=preferences.getString("profileUrl"null);
 
 
    }
}
 
 
 

G.java

1
2
3
4
5
6
7
8
 
public class G {
 
    public static String nickName;
    public static String porfileUrl;
}
 
 
 

MessageItem.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
 
public class MessageItem {
 
    String name;
    String message;
    String time;
    String profileUrl;
 
    public MessageItem(String name, String message, String time, String pofileUrl) {
        this.name = name;
        this.message = message;
        this.time = time;
        this.profileUrl = pofileUrl;
    }
 
    //firebase DB에 객체로 값을 읽어올 때..
    //파라미터가 비어있는 생성자가 핑요함.
    public MessageItem() {
    }
 
    //Getter & Setter
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public String getMessage() {
        return message;
    }
 
    public void setMessage(String message) {
        this.message = message;
    }
 
    public String getTime() {
        return time;
    }
 
    public void setTime(String time) {
        this.time = time;
    }
 
    public String getPofileUrl() {
        return profileUrl;
    }
 
    public void setPofileUrl(String pofileUrl) {
        this.profileUrl = pofileUrl;
    }
}
 
 
 

ChatAdapter.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
 
 
 
 
import de.hdodenhof.circleimageview.CircleImageView;
 
public class ChatAdapter extends BaseAdapter {
 
    ArrayList<MessageItem> messageItems;
    LayoutInflater layoutInflater;
 
    public ChatAdapter(ArrayList<MessageItem> messageItems, LayoutInflater layoutInflater) {
        this.messageItems = messageItems;
        this.layoutInflater = layoutInflater;
    }
 
    @Override
    public int getCount() {
        return messageItems.size();
    }
 
    @Override
    public Object getItem(int position) {
        return messageItems.get(position);
    }
 
    @Override
    public long getItemId(int position) {
        return position;
    }
 
    @Override
    public View getView(int position, View view, ViewGroup viewGroup) {
 
        //현재 보여줄 번째의(position)의 데이터로 뷰를 생성
        MessageItem item=messageItems.get(position);
 
        //재활용할 뷰는 사용하지 않음!!
        View itemView=null;
 
        //메세지가 내 메세지인지??
        if(item.getName().equals(G.nickName)){
            itemView= layoutInflater.inflate(R.layout.my_msgbox,viewGroup,false);
        }else{
            itemView= layoutInflater.inflate(R.layout.other_msgbox,viewGroup,false);
        }
 
        //만들어진 itemView에 값들 설정
        CircleImageView iv= itemView.findViewById(R.id.iv);
        TextView tvName= itemView.findViewById(R.id.tv_name);
        TextView tvMsg= itemView.findViewById(R.id.tv_msg);
        TextView tvTime= itemView.findViewById(R.id.tv_time);
 
        tvName.setText(item.getName());
        tvMsg.setText(item.getMessage());
        tvTime.setText(item.getTime());
 
 
        return itemView;
    }
}
 
 
 

ChatActivity.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
 
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
 
import android.content.Context;
 
 
 
public class ChatActivity extends AppCompatActivity {
 
    EditText et;
    ListView listView;
 
    ArrayList<MessageItem> messageItems=new ArrayList<>();
    ChatAdapter adapter;
 
    //Firebase Database 관리 객체참조변수
    FirebaseDatabase firebaseDatabase;
 
    //'chat'노드의 참조객체 참조변수
    DatabaseReference chatRef;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_chat);
 
        //제목줄 제목글시를 닉네임으로(또는 채팅방)
        getSupportActionBar().setTitle(G.nickName);
 
        et=findViewById(R.id.et);
        listView=findViewById(R.id.listview);
        adapter=new ChatAdapter(messageItems,getLayoutInflater());
        listView.setAdapter(adapter);
 
        //Firebase DB관리 객체와 'caht'노드 참조객체 얻어오기
        firebaseDatabase= FirebaseDatabase.getInstance();
        chatRef= firebaseDatabase.getReference("chat");
 
 
        //firebaseDB에서 채팅 메세지들 실시간 읽어오기..
        //'chat'노드에 저장되어 있는 데이터들을 읽어오기
        //chatRef에 데이터가 변경되는 것으 듣는 리스너 추가
        chatRef.addChildEventListener(new ChildEventListener() {
            //새로 추가된 것만 줌 ValueListener는 하나의 값만 바뀌어도 처음부터 다시 값을 줌
            @Override
            public void onChildAdded(@NonNull DataSnapshot dataSnapshot, @Nullable String s) {
 
                //새로 추가된 데이터(값 : MessageItem객체) 가져오기
                MessageItem messageItem= dataSnapshot.getValue(MessageItem.class);
 
                //새로운 메세지를 리스뷰에 추가하기 위해 ArrayList에 추가
                messageItems.add(messageItem);
 
                //리스트뷰를 갱신
                adapter.notifyDataSetChanged();
                listView.setSelection(messageItems.size()-1); //리스트뷰의 마지막 위치로 스크롤 위치 이동
            }
 
            @Override
            public void onChildChanged(@NonNull DataSnapshot dataSnapshot, @Nullable String s) {
 
            }
 
            @Override
            public void onChildRemoved(@NonNull DataSnapshot dataSnapshot) {
 
            }
 
            @Override
            public void onChildMoved(@NonNull DataSnapshot dataSnapshot, @Nullable String s) {
 
            }
 
            @Override
            public void onCancelled(@NonNull DatabaseError databaseError) {
 
            }
        });
 
    }
 
    public void clickSend(View view) {
 
        //firebase DB에 저장할 값들( 닉네임, 메세지, 프로필 이미지URL, 시간)
        String nickName= G.nickName;
        String message= et.getText().toString();
        String pofileUrl= G.porfileUrl;
 
        //메세지 작성 시간 문자열로..
        Calendar calendar= Calendar.getInstance(); //현재 시간을 가지고 있는 객체
 
        //firebase DB에 저장할 값(MessageItem객체) 설정
        MessageItem messageItem= new MessageItem(nickName,message,time,pofileUrl);
        //'char'노드에 MessageItem객체를 통해
 
        //EditText에 있는 글씨 지우기
        et.setText("");
 
        //소프트키패드를 안보이도록..
        InputMethodManager imm=(InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
        imm.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(),0);
 
        //처음 시작할때 EditText가 다른 뷰들보다 우선시 되어 포커스를 받아 버림.
        //즉, 시작부터 소프트 키패드가 올라와 있음.
 
        //그게 싫으면...다른 뷰가 포커스를 가지도록
        //즉, EditText를 감싼 Layout에게 포커스를 가지도록 속성을 추가!![[XML에]
    }
}
 
 
 

댓글