2016年10月10日 星期一

雲端記事本<二>:新增/刪除記事資料

此篇延續前一篇雲端記事本<一>

加入記事本新增/刪除記事資料的功能

久按記事項目,即會跳出詢問刪除視窗




另外在firebase console裡修改Database的規則如下:
{
  "rules": {
    "users":{
      "$uid":{
        ".read": "$uid === auth.uid",
        ".write": "$uid === auth.uid"
      }
    }
  }
}
讓每個登入的使用者用自身的uid建立資料夾

並且只能存取自己這個資料夾下的資料

首先新增Item class,用來儲存每一筆記事資料的內容
這邊只有兩筆String資料,分別是title & content
> Item.java
package com.pingtung.ccstudio.notepadcloud;

/**
 * Created by crowd_000 on 10/6/2016.
 */

public class Item {
    private String title;
    private String content;

    public Item(){
    }

    public Item(String title, String content) {
        this.title = title;
        this.content = content;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }
}
這邊要為每一筆instance variable,撰寫getter/setter methods 及加入無參數建構子,才能讓firebase database作read/write (若沒宣告會直接compilation failed)

新增ItemPlus class,相較於Item class,多儲存一筆key值,用來刪除資料用
> ItemPlus.java
package com.pingtung.ccstudio.notepadcloud;

/**
 * Created by crowd_000 on 10/6/2016.
 */

public class ItemPlus extends Item {
    private String key;

 public ItemPlus() {
    }
 
    public ItemPlus(Item item, String key) {
        super(item.getTitle(),item.getContent());
        this.key = key;
    }

    public String getKey() {
        return key;
    }

    public void setKey(String key) {
        this.key = key;
    }
}

新增ItemPlusAdapter class,將資料顯示在listView
ItemPlusAdapter.java
package com.pingtung.ccstudio.notepadcloud;

import android.content.Context;
import android.support.annotation.NonNull;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;

import java.util.List;

/**
 * Created by crowd_000 on 10/6/2016.
 */

public class ItemPlusAdapter extends ArrayAdapter<ItemPlus> {
    private Context context;
    private int resource;
    private List<ItemPlus> list;
    private LayoutInflater layoutInflater;

    public ItemPlusAdapter(Context context, int resource, List<ItemPlus> list) {
        super(context, resource, list);
        this.context = context;
        this.resource = resource;
        this.list = list;
        layoutInflater = LayoutInflater.from(context);
    }

    @NonNull
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View itemView;
        ViewHolder viewHolder;
        ItemPlus itemPlus = list.get(position);

        if(convertView==null){
            itemView = layoutInflater.inflate(resource,null);

            viewHolder = new ViewHolder((TextView) itemView.findViewById(R.id.tvTitle),
                    (TextView) itemView.findViewById(R.id.tvContent));

            itemView.setTag(viewHolder);
        }else{
            itemView = convertView;
            viewHolder = (ViewHolder) itemView.getTag();
        }

        viewHolder.tvTitle.setText(itemPlus.getTitle());
        viewHolder.tvContent.setText(itemPlus.getContent());

        return itemView;
    }

    private class ViewHolder{
        TextView tvTitle,tvContent;

        public ViewHolder(TextView tvTitle,TextView tvContent){
            this.tvTitle = tvTitle;
            this.tvContent = tvContent;
        }
    }
}

ListView單行佈局
single_line.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal" android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:gravity="center">

    <TextView
        android:text="TextView"
        android:layout_height="wrap_content"
        android:id="@+id/tvTitle"
        android:textSize="24sp"
        android:layout_width="85dp"
        android:maxLines="1"
        android:gravity="center" />

    <TextView
        android:text="TextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/tvContent"
        android:layout_weight="1"
        android:textSize="14sp"
        android:maxLines="2" />
</LinearLayout>

主畫面佈局新增ListView及EditText
activity_main.xml
<?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:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.pingtung.ccstudio.notepadcloud.MainActivity">

    <TextView
        android:text="LoginUser:"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:id="@+id/textView6"
        android:textSize="18sp"
        android:textColor="@android:color/black" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="UserName"
        android:id="@+id/tvUser"
        android:textSize="18sp"
        android:layout_marginLeft="17dp"
        android:layout_marginStart="17dp"
        android:layout_alignParentBottom="true"
        android:layout_toRightOf="@+id/textView6"
        android:layout_toEndOf="@+id/textView6"
        android:textColor="@android:color/holo_red_light" />

    <ListView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_alignParentTop="true"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:id="@+id/listView"
        android:dividerHeight="2dp"
        android:layout_alignParentRight="true"
        android:layout_alignParentEnd="true"
        android:layout_above="@+id/linearLayout"/>

    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_above="@+id/textView6"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:id="@+id/linearLayout">

        <EditText
            android:layout_height="wrap_content"
            android:inputType="textPersonName"
            android:text=""
            android:ems="10"
            android:id="@+id/etTitle"
            android:layout_width="80dp"
            android:hint="title" />

        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:inputType="textPersonName"
            android:text=""
            android:ems="10"
            android:id="@+id/etContent"
            android:layout_weight="1"
            android:hint="content" />

    </LinearLayout>

</RelativeLayout>

主畫面控制加入新增/刪除記事功能
MainActivity.java
package com.pingtung.ccstudio.notepadcloud;

import android.content.DialogInterface;
import android.content.Intent;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseUser;
import com.google.firebase.database.ChildEventListener;
import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.DatabaseError;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;
import com.google.firebase.database.ValueEventListener;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {
    //UI variables
    private TextView tvUser;
    private ListView listView;
    private EditText etTitle, etContent;

    //餵給listView的相關變數
    private ItemPlusAdapter itemPlusAdapter;
    private List<ItemPlus> itemPlusList;

    //Firebase instance variables
    private FirebaseAuth auth;
    private FirebaseUser user;
    private FirebaseDatabase firebaseDatabase;
    private DatabaseReference mRef;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //local variable
        String userUid="";

        //find UId
        tvUser = (TextView)findViewById(R.id.tvUser);
        listView = (ListView)findViewById(R.id.listView);
        etTitle = (EditText)findViewById(R.id.etTitle);
        etContent = (EditText)findViewById(R.id.etContent);

        //取得auth實體(只有一個實體app內皆能共用)
        auth = FirebaseAuth.getInstance();
        user = auth.getCurrentUser();

        //若無登入帳戶,則getCurrentUser()傳回null
        //開啟LoginActivity
        if(user==null){
            startActivity(new Intent(this,LoginActivity.class));
            finish();
        }else{
            tvUser.setText(user.getEmail());
            userUid = user.getUid();
        }

        //實體化餵給listView的相關變數: arrayList, adapter
        itemPlusList = new ArrayList<>();
        itemPlusAdapter = new ItemPlusAdapter(MainActivity.this,R.layout.single_line,itemPlusList);
        listView.setAdapter(itemPlusAdapter);

        //取得firebase根目錄下"/users/$uid"的reference
        //不同登入的使用者有不同的Uid,無法存取其他人的資料
        firebaseDatabase = FirebaseDatabase.getInstance();
        mRef = firebaseDatabase.getReference("users/"+userUid);

        //對使用者根目錄(/users/$uid)註冊一子目錄變更監聽器
        ChildEventListener childEventListener = new ChildEventListener() {
            @Override
            public void onChildAdded(DataSnapshot dataSnapshot, String s) {
                String key = dataSnapshot.getKey();
                Log.d("android","onChildAdded: "+ key);
                Item item = dataSnapshot.getValue(Item.class);
                ItemPlus itemPlus = new ItemPlus(item, key);
                itemPlusList.add(itemPlus);
            }

            @Override
            public void onChildChanged(DataSnapshot dataSnapshot, String s) {

            }

            @Override
            public void onChildRemoved(DataSnapshot dataSnapshot) {
                String key = dataSnapshot.getKey();
                Log.d("android", "onChildRemoved: " + key);

                ItemPlus tmp=null;
                for(ItemPlus ip:itemPlusList){
                    if(ip.getKey().equals(key)){
                        tmp = ip;
                        break;
                    }
                }
                if(itemPlusList.contains(tmp))
                    itemPlusList.remove(tmp);
            }

            @Override
            public void onChildMoved(DataSnapshot dataSnapshot, String s) {

            }

            @Override
            public void onCancelled(DatabaseError databaseError) {

            }
        };
        mRef.addChildEventListener(childEventListener);

        //註冊使用者根目錄監聽器
        //只要子目錄值一有變更,便會收到通知
        //通知adapter資料有變更,刷新listView顯示
        ValueEventListener valueEventListener = new ValueEventListener() {
            @Override
            public void onDataChange(DataSnapshot dataSnapshot) {
                Log.d("android","onDataChange");
                itemPlusAdapter.notifyDataSetChanged();
            }

            @Override
            public void onCancelled(DatabaseError databaseError) {

            }
        };
        mRef.addValueEventListener(valueEventListener);

        //註冊listView長按事件
        //詢問是否刪除
        listView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
            @Override
            public boolean onItemLongClick(AdapterView<?> adapterView, View view, int position, long l) {
                final ItemPlus itemPlus = itemPlusList.get(position);

                AlertDialog.Builder ab = new AlertDialog.Builder(MainActivity.this);

                ab.setTitle(itemPlus.getTitle())
                        .setMessage("是否刪除此筆紀錄?")
                        .setIcon(R.mipmap.ic_launcher)
                        .setCancelable(true);
                ab.setPositiveButton("確定", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        mRef.child(itemPlus.getKey()).removeValue();
                    }
                });
                ab.setNegativeButton("取消", null);
                ab.show();

                return true;
            }
        });
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu_main,menu);

        return super.onCreateOptionsMenu(menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch(item.getItemId()){
            case R.id.add:
                String title = etTitle.getText().toString();
                String content = etContent.getText().toString();

                if(title.equals("") || content.equals("")){
                    Toast.makeText(MainActivity.this,"請輸入title及content",Toast.LENGTH_SHORT).show();
                }else{
                    //使用push()讓系統自動產生key值
                    //key值依timestamp產生,不會重複且不斷向上遞增
                    mRef.push().setValue(new Item(title,content));
                    etTitle.setText("");
                    etContent.setText("");
                }
                break;
            case R.id.logout:
                //將auth登出
                auth.signOut();
                startActivity(new Intent(this,LoginActivity.class));
                finish();
                break;
        }

        return super.onOptionsItemSelected(item);
    }
}

在menu resource中新增add功能就完成囉
menu_main.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:apps="http://schemas.android.com/apk/res-auto">

    <item android:title="add"
        android:id="@+id/add"
        android:icon="@android:drawable/ic_input_add"
        apps:showAsAction="always"></item>

    <item android:title="logout"
        android:id="@+id/logout"
        apps:showAsAction="never"></item>
</menu>

> 完整程式碼(Github)




沒有留言:

張貼留言