Skip to content

ToMAS_wiki

rlarla915 edited this page Oct 31, 2020 · 5 revisions

1) 자기개발,소통 게시판

1. 동적으로 서버에서 구조를 받아와 구성

기술 자세히
public void show_field()  
{  
    switch (fragment_style)  
    {  
        case 1:  
            root = inflater.inflate(R.layout.template1, container, false);  
  ListView tmp_listview = (ListView)root.findViewById(R.id.template1_listView);  
 final ArrayAdapter<String> listview_adapter = new ArrayAdapter<String>(mainActivity, android.R.layout.simple_list_item_1, child_list); // simple_list_item layout를 바꿔야 style이 바뀜  
  tmp_listview.setAdapter(listview_adapter);  
  // 구조 다시 바꿈. fragment_template에서 child_list를 firebase에서 읽어옴.  
 // firebase에서 child_list채우기  
  mainActivity.db.collection(path).get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {  
            @Override  
  public void onComplete(@NonNull Task<QuerySnapshot> task) {  
                if (task.isSuccessful()) {  
                    child_list.clear();  
  child_fragment_list.clear();  
 for (QueryDocumentSnapshot document : task.getResult()) {  
                        //Log.d(TAG, document.getId() + " => " + document.getData());  
  child_list.add(document.getId());  
  child_fragment_list.add(document.get("fragment_style", Integer.class));  
  }  
                    listview_adapter.notifyDataSetChanged();  
  } else {  
                    //Log.d(TAG, "Error getting documents: ", task.getException());  
  }  
            }  
        });  
  tmp_listview.setOnItemClickListener(new AdapterView.OnItemClickListener() {  
                @Override  
  public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {  
                    fragmentTransaction = fragmentManager.beginTransaction();  
  fragmentTransaction.addToBackStack(null);  
  // 다음 child를 만들고 arg 넘기는 과정  
  // need to fix db에서 받아와서 분기해야댐.  
  Fragment change_fragment = new FragmentTemplate();  
  Bundle args = new Bundle();  
  args.putInt("fragment_style", child_fragment_list.get(i));  
  args.putString("title", child_list.get(i));  
  args.putString("path", path);  
  change_fragment.setArguments(args);  
  fragmentTransaction.replace(R.id.nav_host_fragment, change_fragment).commit();  
  }  
            });  
 break; case 2:  
            root = inflater.inflate(R.layout.template2, container, false);  
 final ListView template2_list = root.findViewById(R.id.template2_list);  
  
  // custom listview를 생성해서 만들어야됨.  
  final Template2ListAdapter template2_adapter = new Template2ListAdapter(mainActivity, fragmentManager);  
  template2_list.setAdapter(template2_adapter);  
  // fragment_style 2에서는 template2_list_adapter에서 클릭을 처리한다.  
  
 // firestore에서 subject list 불러오기.  
  mainActivity.db.collection(path).get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {  
                @Override  
  public void onComplete(@NonNull Task<QuerySnapshot> task) {  
                    if (task.isSuccessful()) {  
                        for (QueryDocumentSnapshot document : task.getResult()) {  
                            String tmp = document.getId();  
 final BoardListAdapter tmp_sample_list_adapter = new BoardListAdapter();  
  // firestore sample list 불러오기  
  mainActivity.db.collection(path + "/" + tmp + "/" + tmp).orderBy("timestamp", Query.Direction.DESCENDING).limit(5)  
                                    .get()  
                                    .addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {  
                                        @Override  
  public void onComplete(@NonNull Task<QuerySnapshot> task) {  
                                            if (task.isSuccessful()) {  
                                                for (QueryDocumentSnapshot document : task.getResult()) {  
                                                    Log.d("QQQ", document.get("title").toString());  
  SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");  
  String dateString = formatter.format(document.get("timestamp", Timestamp.class).toDate());  
  tmp_sample_list_adapter.addItem(document.get("title").toString(), document.get("num_comments").toString(), dateString, document.get("writer").toString(), document.get("clicks").toString(), document.getId());  
  }  
                                                tmp_sample_list_adapter.notifyDataSetChanged();  
  } else {  
                                                //Log.d(TAG, "Error getting documents: ", task.getException());  
  }  
                                        }  
                                    });  
  template2_adapter.addItem(tmp, tmp_sample_list_adapter, path);  
  }  
                        template2_adapter.notifyDataSetChanged();  
  
  } else {  
                        //Log.d(TAG, "Error getting documents: ", task.getException());  
  }  
                }  
            });  
 break; case 3:  
            root = inflater.inflate(R.layout.template3, container, false);  
  ListView tmp3_listview = root.findViewById(R.id.template3_listView);  
 final BoardListAdapter adapter = new BoardListAdapter();  
  tmp3_listview.setAdapter(adapter);  
  
  // need to fix addItem에 서버에서 받아온 db를 넣어야 함.  
  mainActivity.db.collection(path).orderBy("timestamp", Query.Direction.DESCENDING)  
                .get()  
                .addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {  
                    @Override  
  public void onComplete(@NonNull Task<QuerySnapshot> task) {  
                        if (task.isSuccessful()) {  
                            for (QueryDocumentSnapshot document : task.getResult()) {  
                                Log.d("AAA", document.get("title").toString());  
  SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");  
  String dateString = formatter.format(document.get("timestamp", Timestamp.class).toDate());  
  adapter.addItem(document.get("title").toString(), document.get("num_comments").toString(), dateString, document.get("writer").toString(), document.get("clicks").toString(), document.getId());  
  }  
                            adapter.notifyDataSetChanged();  
  } else {  
                            //Log.d(TAG, "Error getting documents: ", task.getException());  
  }  
                    }  
                });  
  
  // click함수에서 key값을 넘겨서 게시판 db에서 가져온 데이터를 넣어야 함.  
  tmp3_listview.setOnItemClickListener(new AdapterView.OnItemClickListener() {  
                @Override  
  public void onItemClick(AdapterView parent, View v, int position, long id) {  
                    fragmentTransaction = fragmentManager.beginTransaction();  
  fragmentTransaction.addToBackStack(null);  
  Fragment change_fragment = new BoardContent();  
  // 게시판 id와 path를 받아와서 board_content fragment로 넘긴다.  
 // maybe 이거 구조를 검색해서 바꿔야 할 듯  
  Bundle args = new Bundle();  
  args.putString("post_id", adapter.listViewItemList.get(position).getId());  
  args.putString("path", path);  
  change_fragment.setArguments(args);  
  fragmentTransaction.replace(R.id.nav_host_fragment, change_fragment).commit();  
  }  
            });  
  
  // fab버튼 관리  
  FloatingActionButton fab = root.findViewById(R.id.fab_board_register);  
  fab.setOnClickListener(new View.OnClickListener() {  
                @Override  
  public void onClick(View view) {  
                    Intent intent = new Intent(mainActivity, RegisterBoardContent.class);  
  intent.putExtra("path", path);  
  intent.putExtra("post_id", "");  
  startActivityForResult(intent, 11111);  
  }  
            });  
  
 break; default:  
            break;  
  }  
}

board_firestore_example

각 구조 field에 fragment_style로 저장되어 있는 값을 불러와서 게시판 자식 목록을 구성합니다.

pathfragment_style을 부모 FragmentTemplate.java로 부터 Bundle argument로 받아 내용물을 구성합니다. path는 현재 게시판 목록의 firestore에서의 위치를 변수로 갖습니다. 각 fragment_style일 때 필요한 값을 firestore에서 불러와서 리스트 adapter에 추가하고, Adpater.notifyDataSetChanged()함수를 통해 바뀐 list를 보여줍니다. 관리자가 firestore에 fragment_style을 기입한 document를 추가하기만 하면 손쉽게 게시판을 추가할 수 있습니다.


2. Html wysiwyg editor를 사용

기술 자세히

사용 라이브러리 : https://github.com/irshuLx/Android-WYSIWYG-Editor

implementation 'com.github.irshulx:laser-native-editor:3.0.3'
@Override  
  public void onUpload(Bitmap image, final String uuid) {  
        Toast.makeText(RegisterBoardContent.this, uuid, Toast.LENGTH_LONG).show();  
  ByteArrayOutputStream baos = new ByteArrayOutputStream();  
  image.compress(Bitmap.CompressFormat.JPEG, 100, baos);  
  storageRef = storage.getReference().child("images/"+uuid+".jpg");  
  
 final byte[] data = baos.toByteArray();  
  
  UploadTask uploadTask = storageRef.putBytes(data);  
  
  Task<Uri> urlTask = uploadTask.continueWithTask(new Continuation<UploadTask.TaskSnapshot, Task<Uri>>() {  
            @Override  
  public Task<Uri> then(@NonNull Task<UploadTask.TaskSnapshot> task) throws Exception {  
                if (!task.isSuccessful()) {  
                    throw task.getException();  
  }  
  
                // Continue with the task to get the download URL  
  return storageRef.getDownloadUrl();  
  }  
        }).addOnCompleteListener(new OnCompleteListener<Uri>() {  
            @Override  
  public void onComplete(@NonNull Task<Uri> task) {  
                if (task.isSuccessful()) {  
                    Uri downloadUri = task.getResult();  
  editor.onImageUploadComplete(downloadUri.toString(), uuid);  
  } else {  
                    // Handle failures  
 // ...  }  
            }  
        });  
}

editor에서 이미지를 앨범에서 불러와 넣을 때, Html코드 <img src =""/>로 변환되어 들어갑니다. 이때 firestore내에 이미지를 저장한 뒤, getDownloadUrl()함수를 통해 url을 받고 이를 src 안에 넣어줍니다.


3. 수정, 삭제 기능을 제공

기술 자세히

RegisterBoardContent.java를 불러올 때 신규 글 쓰기의 경우 인자로 빈 String을 넘겨주고, 수정하기 경우엔 해당 글의 post_id을 인자로 넘겨주어서, If문을 통해 분기합니다. 수정하기 일 때는 기존에 썼던 내용을 editor에 넣어줍니다. 글을 서버에 올릴 때는 신규 글 쓰기의 경우 새로운 uuid를 통해 post_id를 정해주고, 수정하기의 경우 기존의 post_id에 set()을 하여 업데이트합니다.

// 새로 글 쓸 때  
if (post_id.equals("")) {  
    db.collection(path).document()  
            .set(post)  
            .addOnSuccessListener(new OnSuccessListener<Void>() {  
                @Override  
  public void onSuccess(Void aVoid) {  
                    setResult(Activity.RESULT_OK);  
  finish();  
  Log.d("AAA", "DocumentSnapshot successfully written!");  
  }  
            })  
            .addOnFailureListener(new OnFailureListener() {  
                @Override  
  public void onFailure(@NonNull Exception e) {  
                    Log.w("AAA", "Error writing document", e);  
  }  
            });  
}  
else {// 수정할 때  
  db.collection(path).document(post_id)  
            .set(post)  
            .addOnSuccessListener(new OnSuccessListener<Void>() {  
                @Override  
  public void onSuccess(Void aVoid) {  
                    setResult(Activity.RESULT_OK);  
  finish();  
  Log.d("AAA", "DocumentSnapshot successfully written!");  
  }  
            })  
            .addOnFailureListener(new OnFailureListener() {  
                @Override  
  public void onFailure(@NonNull Exception e) {  
                    Log.w("AAA", "Error writing document", e);  
  }  
            });  
}

삭제하기의 경우 firestore 자체에서 collection이 포함된 document 삭제를 제공하지 않았습니다.

저희 데이터베이스 구조에서는 게시물 document에 댓글 collection까지 포함되어 있었기 때문에 댓글 document까지 삭제하는 것을 client에서 처리하였습니다.

delete_button.setOnClickListener(new View.OnClickListener() {  
    @Override  
  public void onClick(View view) {  
        mPostReference.collection("comments").get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {  
            @Override  
  public void onComplete(@NonNull Task<QuerySnapshot> task) {  
                if (task.isSuccessful()) {  
                    for (QueryDocumentSnapshot document : task.getResult()) {  
                        mPostReference.collection("comments").document(document.getId()).delete();  
  }  
                    mPostReference.delete();  
  } else {  
                    //Log.d(TAG, "Error getting documents: ", task.getException());  
  }  
            }  
        });  
  fragmentManager.beginTransaction().remove(BoardContent.this).commit();  
  fragmentManager.popBackStack();  
  }  
});

2) 플리마켓

1. 부대 단위로 데이터베이스 분리

기술 자세히

market_firestore

데이터베이스를 분리하고 각 부대에 대한 path를 구해 데이터베이스에 접근했습니다. path를 구하는 방법은 아래와 같습니다. [소속tree 클릭하는 이미지 넣기]

사용 라이브러리 : https://github.com/TellH/RecyclerTreeView 해당 라이브러리는 Import module한 뒤, 커스텀 해 사용했습니다.

소속 선택을 처리하기 위해서 RecyclerTreeView 라이브러리를 이용했습니다. 서버에서 소속 구조를 받아와 RecyclerTreeView를 구성합니다. firestore에 구조 하나만 추가해서 새로운 소속의 부대를 추가할 수 있습니다. 삭제는 firestore에서 해당 부대의 하위 document와 collection을 지워주기만 하면 됩니다. 동적으로 부대를 추가하고 삭제할 수 있어서 확장성이 좋습니다.

BelongTreeDialog.java

public void init_tree(TreeNode<Dir> node)  
{  
    // 자기자신의 path까지 node로 저장하고 이를 firebase path에 넘겨줌.  
  FirebaseFirestore.getInstance().collection(node.getPath())  
            .get()  
            .addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {  
                @Override  
  public void onComplete(@NonNull Task<QuerySnapshot> task) {  
                    if (task.isSuccessful()) {  
                        for (QueryDocumentSnapshot document : task.getResult()) {  
                            String tmp_path = node.getPath() + "/" + document.getId() + "/" + document.getId();  
  TreeNode<Dir> tmp_node = new TreeNode<>(new Dir(document.getId()), tmp_path);  
  init_tree(tmp_node);  
  node.addChild(tmp_node);  
  }  
                        if (check_first) {  
                            adapter.refresh(nodes);  
  check_first = false;  
  }  
                    } else {  
                    }  
                }  
            });  
}

라이브러리의 TreeNode<Dir> node를 커스터마이징해서 소속 부대의 path를 저장할 수 있는 객체로 만들었습니다. firestore의 소속 부대 구조에 자식 document가 있다면, 계속 탐색해서 새로운 node를 생성하고 부모 node에 연결합니다. 터치 시엔 view의 background에 색을 줘서 어떤 것이 마지막에 선택되었는지 체크할 수 있게 합니다. 이후 확인 버튼을 눌러 Dialog를 끄게 되면, 마지막에 눌렸던 node의 path를 getPath()를 통해 불러와 BelongTreeDialog를 호출한 fragment에 path값을 전달해주었습니다. 이는 interface를 통해 구현되었습니다.


2. 최고가 입찰자가 수령 완료 버튼을 누르면 포인트 결산

기술 자세히
mainActivity.db.collection("user").document(mainActivity.getUid()).collection("buy_list").document(list_adapter.listViewItemList.get(i).getId()).get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {  
    @Override  
  public void onComplete(@NonNull Task<DocumentSnapshot> task) {  
        if (task.isSuccessful()) {  
            DocumentSnapshot document = task.getResult();  
 if (document.exists()) {  
                ShowBuyDialog dialog = new ShowBuyDialog(mainActivity, document.get("title").toString(), document.get("due_date").toString(), document.get("place").toString(), document.get("price", Integer.class));  
  dialog.show(mainActivity.getSupportFragmentManager(), "구매확정");  
  dialog.setDialogResult(new ShowBuyDialog.show_buy_dialog_result() {  
                    @Override  
  public void get_result() {  
                        // point_record 업데이트  
  Map<String, Object> post = new HashMap<>();  
  post.put("title", "플리마켓 구매 : " + document.get("title").toString());  
  post.put("timestamp", FieldValue.serverTimestamp());  
  post.put("change", "-" + Integer.toString(document.get("price", Integer.class)));  
  mainActivity.db.collection("user").document(mainActivity.getUid()).collection("point_record").document()  
                                .set(post)  
                                .addOnSuccessListener(new OnSuccessListener<Void>() {  
                                    @Override  
  public void onSuccess(Void aVoid) {  
  
                                    }  
                                })  
                                .addOnFailureListener(new OnFailureListener() {  
                                    @Override  
  public void onFailure(@NonNull Exception e) {  
                                    }  
                                });  
  Map<String, Object> post2 = new HashMap<>();  
  post2.put("title", "플리마켓 판매 : " + document.get("title").toString());  
  post2.put("timestamp", FieldValue.serverTimestamp());  
  post2.put("change", "+" + Integer.toString(document.get("price", Integer.class)));  
  mainActivity.db.collection("user").document(document.get("seller_id").toString()).collection("point_record").document()  
                                .set(post2)  
                                .addOnSuccessListener(new OnSuccessListener<Void>() {  
                                    @Override  
  public void onSuccess(Void aVoid) {  
  
                                    }  
                                })  
                                .addOnFailureListener(new OnFailureListener() {  
                                    @Override  
  public void onFailure(@NonNull Exception e) {  
                                    }  
                                });  
  // point 업데이트  
  mainActivity.db.collection("user").document(mainActivity.getUid()).update("point", FieldValue.increment(document.get("price", Integer.class)));  
  mainActivity.db.collection("user").document(document.get("seller_id").toString()).update("point", FieldValue.increment(document.get("price", Integer.class)*-1));  
  // user 내의 구매확정 목록 삭제  
  mainActivity.db.collection("user").document(document.get("seller_id").toString()).collection("market").document(document.get("post_id").toString()).delete();  
  mainActivity.db.collection("user").document(mainActivity.getUid()).collection("buy_list").document(list_adapter.listViewItemList.get(i).getId()).delete()  
                                .addOnSuccessListener(new OnSuccessListener<Void>() {  
                                    @Override  
  public void onSuccess(Void aVoid) {  
                                        // fragment 뒤로가기  
  fragmentManager.beginTransaction().remove(BuyList.this).commit();  
  fragmentManager.popBackStack();  
  }  
                                })  
                                .addOnFailureListener(new OnFailureListener() {  
                                    @Override  
  public void onFailure(@NonNull Exception e) {  
                                    }  
                                });  
  }  
                });  
  
  }  
        }  
    }  
});

BuyList.java에 구매 목록을 listView로 구성합니다.

market_point_firestore

마감일이 지나면 최고가 입찰자의 buy_list collection에 구매한 항목이 저장됩니다. 이 document에는 field가 5가지가 있습니다. 그 중 seller_id는 판매자의 유저 ID를 저장하고 있습니다. 이것을 통해 판매자의 user document에 접근할 수 있고, 그 중 point_record collection에 판매한 내역, 변화한 포인트, timestamp를 저장해 포인트 변화 내역을 기록합니다. 구매자도 마찬가지로 포인트 변화 내역을 저장합니다.


3) 인원모집

1. 부대 단위로 데이터베이스 분리

기술 자세히

enter image description here

마찬가지로 RecylcerTreeView를 사용해 소속 부대를 변경할 수 있게 했습니다.


2. 참가 신청/취소 기능을 제공

기술 자세히

SlidingUpPanelLayout 라이브러리를 이용해 구현했습니다.

btn_enroll.setOnClickListener(new Button.OnClickListener() {  
    @Override  
  public void onClick(View view) {  
        // user ID를 통해 검색하고 list에 있으면 없애고, list에 없으면 추가하기)  
  String mUid = mainActivity.getUid();  
 if (tmp_participants.containsKey(mainActivity.getUid()))  
        { // 이미 참가자에 uid가 있는 경우 : array에서 삭제  
  mPostReference.update("participants."+mUid , FieldValue.delete());  
  mPostReference.update("now_people", FieldValue.increment(-1));  
  // fragment 새로고침  
  FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();  
  fragmentTransaction.detach(GroupContent.this).attach(GroupContent.this).commit();  
  }  
        else {  
            if (slidingUpPanelLayout.getPanelState() == SlidingUpPanelLayout.PanelState.EXPANDED)  
            {  
                Map<String, String> my_info = new HashMap<>();  
  my_info.put("name", mainActivity.preferences.getString("이름", ""));  
  my_info.put("phonenumber", mainActivity.preferences.getString("phonenumber", ""));  
  my_info.put("position", position_edit.getText().toString());  
  mPostReference.update("participants." + mUid, my_info);  
  mPostReference.update("now_people", FieldValue.increment(1));  
  
  // fragment 새로고침  
  FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();  
  fragmentTransaction.detach(GroupContent.this).attach(GroupContent.this).commit();  
  }  
            else  
  {  
                slidingUpPanelLayout.setPanelState(SlidingUpPanelLayout.PanelState.EXPANDED);  
  }  
        }  
    }  
});

새로운 참가자가 참가 신청 버튼을 누르면 SlidingLayout이 위로 올라오는 건 setPanelState(SlidingUpPanelLayout.PanelState.EXPANDED)로 구현했습니다.

group_firebase

group_paricipants_example update()함수와 Map field 명 뒤에 "."을 붙이는 방법을 사용해서 Map에 새로운 Map을 추가합니다. name, phonenumber, position key 세 개를 채운 Map을 value로 유저의 ID를 key로 participants에 추가합니다.

participants에 유저 ID가 있는지 확인하고 있으면 참가 신청 버튼을 취소 버튼으로 바꿉니다. 참가 취소 버튼을 누르면 participants에 있는 유저 ID 키와 value를 Field.delete()로 지웁니다.

사용 라이브러리 : https://github.com/umano/AndroidSlidingUpPanel

implementation 'com.sothree.slidinguppanel:library:3.4.0'

4) 설문조사

1. 관리자가 객관식과 주관식 두 가지 분류로 설문조사를 등록할 수 있다.

기술 자세히
radioButton1.setOnClickListener(new OnClickListener() {  
    @Override  
  public void onClick(View view) {  
        container.removeAllViews();  
  Button add_button = new Button(tmp_context);  
  add_button.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));  
  add_button.setText("객관식 보기 추가");  
  add_button.setOnClickListener(new OnClickListener() {  
            @Override  
  public void onClick(View view) {  
                MultipleChoiceSelectionCustomView tmp_selection_customView = new MultipleChoiceSelectionCustomView(tmp_context);  
  tmp_selection_customView.delete_button.setOnClickListener(new OnClickListener() {  
                    @Override  
  public void onClick(View view) {  
                        container.removeView(tmp_selection_customView);  
  multi_chice_selection_list.remove(tmp_selection_customView.multiple_choice_index);  
  recount();  
  }  
                });  
  container.addView(tmp_selection_customView, multi_chice_selection_list.size());  
  multi_chice_selection_list.add(tmp_selection_customView);  
  recount();  
  }  
        });  
  container.addView(add_button);  
  }  
});  
radioButton2.setOnClickListener(new OnClickListener() {  
    @Override  
  public void onClick(View view) {  
        container.removeAllViews();  
  EditText tmp_edit = new EditText(tmp_context);  
  tmp_edit.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));  
  container.addView(tmp_edit);  
  }  
});

customView를 통해 문항 추가 기능을 구현했습니다. 오른쪽 아래의 +버튼을 눌러 문항을 추가하면, SurveyQuestionCustomView.javaRegisterSocialSurvey.java에 넣어줍니다. RadioButton로 객관식 주관식을 선택할 수 있고, 각각의 경우 기존의 container에 있던 내용을 지우고 새로 형식을 구성합니다.


2. 관리자가 개개인의 설문조사 결과와 종합된 결과를 확인할 수 있다.

기술 자세히

마찬가지로 customView를 이용해 표현했습니다.

불러오는 정보의 데이터 구조는 다음과 같습니다.

firestore 구조

개인 설문 조사 결과 SurveyContentResultIndividual.java

mPostReference.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {  
    @Override  
  public void onComplete(@NonNull Task<DocumentSnapshot> task) {  
        if (task.isSuccessful()) {  DocumentSnapshot document = task.getResult();  
 if (document.exists()) { title_textView.setText(document.get("title", String.class));  
  due_date_textView.setText(document.get("due_date", String.class));  
  writer_textView.setText(document.get("writer", String.class));  
  mPostReference.collection("submissions").document(participant_id).get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {  
                    @Override  
  public void onComplete(@NonNull Task<DocumentSnapshot> task) {  
                        if (task.isSuccessful()) {   DocumentSnapshot adocument = task.getResult();  
 if (adocument.exists()) {  answers_list = (ArrayList<String>) adocument.get("answers");    mPostReference.collection("questions").orderBy("index").get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {  
                                    @Override  
  public void onComplete(@NonNull Task<QuerySnapshot> task) {  
                                        if (task.isSuccessful()) {  
                                            int count = 1;  
 for (QueryDocumentSnapshot tmp_document : task.getResult()) {  
                          int tmp_type = tmp_document.get("type", Integer.class);  
  String item_question = tmp_document.get("question", String.class);  
  SurveyContentResultCustomView tmp_customView;  
 if (tmp_type == 1) {  
  tmp_customView = new SurveyContentResultCustomView(mainActivity, null, tmp_type, count, item_question, (ArrayList<String>) tmp_document.get("multi_choice_questions"), answers_list.get(count-1));  
  } else {  
  tmp_customView = new SurveyContentResultCustomView(mainActivity, null, tmp_type, count, item_question, null, answers_list.get(count-1));  
  }    container_linearLayout.addView(tmp_customView, count + 1);  
  count++;  
  }  
                                        }  
                                    }  
                                });  
  }  
                        }  
                    }  
                });  
  }  
        }  
    }  
});

SurveryContentResultTotal.java 참고

submissions collection에서 개인 사용자의 ID로 document를 얻어냅니다. document 내의 answers field에는 순서대로 index에 대한 개인 사용자의 설문조사 제출 결과가 들어있습니다. 이걸 먼저 받아온 뒤, question collection에서 받아온 질문과 결합해 SurveyContentResultCustomView로 넘겨줍니다. SurveyContentResultCustomView에서는 questions collection에서 받아온 document들을 index로 정렬하고 type에 따라 항목을 구성한 뒤, type이 1인 경우(객관식) multi_choice_questions를 가지고 객관식 선택지를 만듭니다. type이 2인 경우(주관식) question만 받아와 TextView에 넣어줍니다.

전체 설문 조사 결과 개인 설문 조사 결과와 유사하게 구성하나, submissions collection에서 모든 document를 for 문을 통해 접근한뒤, 결과를 종합해서 보여준다는 차이점이 있습니다. 전체 설문 조사 결과에서는 RadioButton을 설정하지 않고, submissions에서 가져온 결과를 넣어줍니다.


5) 기타

1. 로그인

기술자세히
LoginActivity.java

    private boolean isValidEmail() {  
    if (email.isEmpty()) {  
        // 이메일 공백  
  Toast.makeText(LoginActivity.this,"이메일 칸을 기입해주세요", Toast.LENGTH_SHORT).show();  
 return false;  } else if (!Patterns.EMAIL_ADDRESS.matcher(email).matches()) {  
        // 이메일 형식 불일치  
  Toast.makeText(LoginActivity.this,"이메일 형식을 따라야 합니다.", Toast.LENGTH_SHORT).show();  
 return false;  } else {  
        return true;  
  }  
}

private boolean isValidPasswd() {  
    if (password.isEmpty()) {  
        // 비밀번호 공백  
  Toast.makeText(LoginActivity.this,"비밀번호 칸을 기입해주세요 ", Toast.LENGTH_SHORT).show();  
 return false;  } else if (!PASSWORD_PATTERN.matcher(password).matches()) {  
        // 비밀번호 형식 불일치  
  Toast.makeText(LoginActivity.this,"비밀번호는 6자이상 16자리이하 입니다. ", Toast.LENGTH_SHORT).show();  
 return false;  } else {  
        return true;  
  }  
}

LoginActivity에있는 이 isValidEmail 함수와 isValidPasswd 함수를 통해 이메일과 비밀번호의 유효성 검사를 1차적으로 합니다.

private void loginUser(String email, String password)  
{  
    customProgressDialog.show(); //로딩창  
  firebaseAuth.signInWithEmailAndPassword(email, password)  
            .addOnCompleteListener(this, new OnCompleteListener<AuthResult>() {  
                @Override  
  public void onComplete(@NonNull Task<AuthResult> task) {  
                    if (task.isSuccessful()) {  
                        // 로그인 성공  
  //Toast.makeText(loginactivity.this, R.string.success_login, Toast.LENGTH_SHORT).show();  
  
  
  if(firebaseAuth.getCurrentUser().isEmailVerified()) {  
                            customProgressDialog.dismiss();  
  Intent intent = new Intent(LoginActivity.this, MainActivity.class);  
  // activiy 스택 삭제  
  intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);  
  intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);  
  startActivity(intent);  
  
  }  
                        else{  
                            customProgressDialog.dismiss();  
  Intent intent = new Intent(LoginActivity.this, EmailVerify.class);  
  
  startActivity(intent);  
  
  
  }  
                    } else {  
                        // 로그인 실패  
  Toast.makeText(LoginActivity.this, R.string.failed_login, Toast.LENGTH_SHORT).show();  
  customProgressDialog.dismiss();  
  }  
                }  
            });  
}

1차 유효성 검사를 통과하면 상기의 loginUser함수를 호출합니다. loginUser 함수는 2가지의 부분으로 나누어져 있습니다.

  1. FireAuth 를 통해 아이디와 비밀번호를 확인하여 문제가 있는지 확인하고,없다면 현재 이 애플리케이션에 login한 사용자가 누구인지 식별하는데 도움을 줄 수 있도록 로그인을 하는 부분

  2. login을 한 사용자가 이메일 인증을 받았는지 확인하여 안 받았다면 받을 수 있는 화면으로 이동하고, 받았다면 바로 MainActivity로 전환하는 부분

이 2가지 부분을 통해 사용자에게 알맞는 다음 화면을 제공합니다.


2. 회원가입

기술 자세히 내용
SignUpActivity.java

private void createUser(String email, String password) {  
    customProgressDialog.show();  
  firebaseAuth.createUserWithEmailAndPassword(email, password)  
            .addOnCompleteListener(this, new OnCompleteListener<AuthResult>() {  
                @Override  
  public void onComplete(@NonNull Task<AuthResult> task) {  
                    if (task.isSuccessful()) {  
                        // 회원가입 성공  
  updateuser();  
  customProgressDialog.dismiss();  
  Intent intent=new Intent(SignUpActivity.this, EmailVerify.class);  
  startActivity(intent);  
  Toast.makeText(SignUpActivity.this, R.string.success_signup, Toast.LENGTH_SHORT).show();  
  
  } else {  
                        // 회원가입 실패  
  Toast.makeText(SignUpActivity.this, "중복된 아이디 입니다.", Toast.LENGTH_SHORT).show();  
  customProgressDialog.dismiss();  
  }  
                }  
            });  
  
}

로그인 기능과 같이 1차 유효성 검사를 마치고 나서 상기의 createUser함수를 호출합니다. 만약 1차 유효성검사를 통과했지만 아이디가 생성이 되지 않는 다면 중복 아이디로 간주하고 사용자에게 Toast 메세지를 띄워 줍니다.

무사히 아이디가 생성되면 updateuser 함수를 호출합니다.

SignUpActivity.java

private void updateuser(){  
    FirebaseFirestore db = FirebaseFirestore.getInstance();  
  EditText edit_phone = findViewById(R.id.edit_phone);  
  EditText edit_name = findViewById(R.id.edit_name);  
  EditText et_email = findViewById(R.id.et_email);  
  EditText et_password = findViewById(R.id.et_password);  
  EditText edit_belong = findViewById(R.id.edit_belong);  
  EditText edit_class = findViewById(R.id.edit_class);  
  EditText edit_armynumber = findViewById(R.id.edit_armynumber);  
  EditText edit_birth = findViewById(R.id.edit_birth);  
  String phone = edit_phone.getText().toString();  
  String name = edit_name.getText().toString();  
  String email = et_email.getText().toString();  
  String password = et_password.getText().toString();  
  String armyclass = edit_class.getText().toString();  
  String armynumber = edit_armynumber.getText().toString();  
  String birth = edit_birth.getText().toString();  
  
  Map<String, Object> upload = new HashMap<>();  
  upload.put("이름",name);  
  upload.put("소속",belong_path);  
  upload.put("권한","사용자");  
  upload.put("군번",armynumber);  
  upload.put("계급",armyclass);  
  upload.put("point",0);  
  upload.put("phonenumber",phone);  
  upload.put("password",password);  
  upload.put("email",email);  
  upload.put("birth",birth);  
  
  //사용자이름, 시간 등등 추가해야 함.  
  
  String tmp_path_list[] = belong_path.split("/");  
  String tmp_path = belong_path.substring(0, belong_path.length() - tmp_path_list[tmp_path_list.length - 1].length());  
  tmp_path += "부대원";  
  Map<String, Object> belong_upload = new HashMap<>();  
  belong_upload.put("name", name);  
  db.collection(tmp_path).document(firebaseAuth.getCurrentUser().getUid()).set(belong_upload);  
  
  db.collection("user").document(firebaseAuth.getCurrentUser().getUid())  
            .set(upload)  
            .addOnSuccessListener(new OnSuccessListener<Void>() {  
                @Override  
  public void onSuccess(Void aVoid) {  
                    Log.d("AAA", "DocumentSnapshot successfully written!");  
  }  
            })  
            .addOnFailureListener(new OnFailureListener() {  
                @Override  
  public void onFailure(@NonNull Exception e) {  
                    Log.w("AAA", "Error writing document", e);  
  }  
            });  
  
  
}

HashMap upload를 생성하여 입력 받은 정보들을 집어넣은 다음 Firestore의 user컬렉션에 저장합니다. 입력받지 않는 정보인 point는 0으로 설정하고 입력 받은 소속을 파싱하여 그 소속의 부대원으로 추가합니다.

이렇게 update함수 까지 호출한 다음 이메일 인증 액티비티로 전환합니다.

EmailVerify.java

firebaseAuth.getCurrentUser().sendEmailVerification()

이메일 인증 액티비티에서 "전송하기"버튼을 누르면 상기의 함수가 호출됩니다. 회원가입시 입력했던 이메일로 인증이메일이 전송되어 [이메일사진] 인증하기 버튼을 누르면 인증이 완료됩니다.

EmailVerify.java

firebaseAuth.getCurrentUser().isEmailVerified()

인증완료후 회원가입버튼을 누르면 위의 함수를 이용하여 현재 로그인된 아이디가 인증 여부를 체크하여 MainActivity로 이동하거나 사용자에게 Toast 메세지를 띄워줍니다.