wasent개발하면서 알게 된점

  • addListenerForSingleValueEvent 를 통해서 snapshot을 얻어올수도 있지만 최초로 addCildEventListener를 node에 연결할때 되돌아 오는 snapshot을 이용할수 있다. 단 addCildEventListener 의 경우 listener가 연결된 지점이하 child 들의 snapshot이 한번에 오는 것이 아니고 순차적으로 하나씩오게 된다. 
  • node의 key, value개념과 child개념을 혼동하면 안된다. 
  • children을 통해 child들을 통으로 가져오려는 경우 children의 parent node에 listener를 연결한다. 때때로 wrapper parent를 만들어야 하는경우도 있다.
  • getValue()사용시 collection형태로 data를 가져오려는 경우 GenericTypeIndicator를 이용한다. 
val type = object : GenericTypeIndicator<HashMap<String,String>>() {}

            val precios : HashMap<String,String>  = dataSnapshot.getValue(type!!)
            liveData.postValue(precios)

ref)kotlin https://stackoverflow.com/a/52684990/3151712

ref)official docs  https://firebase.google.com/docs/reference/android/com/google/firebase/database/GenericTypeIndicator

.

.

.

.

.

google firebase realtime database official docs
https://firebase.google.com/docs/database/android/start

To make your app data update in realtime, you should add a ValueEventListener to the reference you just created. (ChildEventListerner는 현 node아래 child를 관찰하는 것이고 ValueEventListener는 현재node 및 child를 관찰하는 것이다)

The onDataChange() method in this class is triggered once when the listener is attached and again every time the data changes, including the children.

All Firebase Realtime Database data is stored as JSON objects. 그러므로 json으로 표현되기 쉬운 형태로 database구성을 생각한다. 

jacob.body.head.eyes.pupil 이런식으로 저장된다고 생각하고 jacob body head eyes pupil은 key이름이며 이안에 다양한 타입의 data가 들어있다. 또 jacob boddy head eyes pupil 은 각각 node이다. 

t’s best to keep your data structure as flat as possible. 너무 많이 nested되지 않게 하며 각 node의 데이터를 가져온다는 이야기는 하부의 모든 데이터도 같이 가져온다는 이야기가 되기 때문에 염두에 둔다.

https://firebase.google.com/docs/database/android/read-and-write

각 node의 reference에서 setValue()를 통해 값을 쓰고 교환하고 지우기(null을 설정)를 할수 있다.

저장가능한 데이터 형태는 아래와 같다.

Pass types that correspond to the available JSON types as follows:

  • String
  • Long
  • Double
  • Boolean
  • Map<String, Object>
  • List<Object>

Pass a custom Java object, 

  • if the class that defines it has a default constructor that takes no arguments and has public getters for the properties to be assigned.

To read data at a path and listen for changes, use the  addValueEventListener()  or  addListenerForSingleValueEvent()  method to add a ValueEventListener to a DatabaseReference. (addValueEventListener 나 addListenerForSingleValueEvent 둘다 ValueEventListener를 사용하는 것은 같다.  다만addListenerForSingleValueEvent는 한번 데이터 snapshot을 가져오고 listener 스스로 삭제된다)

image

위 그림에서 ValueEventListener also defines the onCancelled() method that is called if the read is canceled. For example, a read can be canceled if the client doesn’t have permission to read from a Firebase database location.

updateChildren()을 이용 해당 node의 여러 child node의 데이터를 하나의 작업으로 수정가능하다. (update만 가능한게 아니라 create도 가능하다.)

image

Simultaneous updates made this way are atomic: either all updates succeed or all updates fail.

setValue() and updateChildren() 작업에는 아래와 같이 listener를 달아서 바로 결과를 확인할수 있다. (setValue()로 create, delete,update작업을 할수 있으므로 모든 작업에 listener를 아래와 같이 달아서 확인 가능) 

image

특정 node에 덧붙여진 listener를 removeEventListener() 를 통해 제거해야 하며 덧붙여진 갯수 만큼 제거 해야 하고 . child node에 있는 것이 자동으로 제거되는 건 아니므로 child node에 따로 접근 제거해야 한다.

https://firebase.google.com/docs/database/android/lists-of-data

collection 형태의 자료를 가지고 있는 node의 경우 node에 ChildEventListener를 붙이면 onChildAdded, onChildChanged, onChildRemoved 을 통해 돌아 오는 snapshot이 각각 하나의 element가 된다. ValueEventListener를 사용하는 경우는 snapshot이 collection전체를 가지고 있게 된다. 이때    for (postSnapshot in dataSnapshot.children) {}    이런 형태로 접근하게 된다.

image

데이터 정렬방법은 위와 같으며 단 한개만 설정할수 있다.

image

위그림은 먼저 정렬된 상태에서 ChildEventListener를 붙이는 모습을 보여준다.

image

위그림은 nested child를 경로 지정으로 정렬기준을 조정하는 방법

image

위 query method는 여러개를 사용할수 있다.

query 성능 개선을 위해서는 index를 이용할수 있다. ( https://firebase.google.com/docs/database/security/indexing-data )

.

.

.

.

original source : https://www.youtube.com/playlist?list=PLk7v1Z2rk4hg3cbALQuTQOTgpZ9e9T8bi

image

.

.

image

.

.

image

addListenerForSingleValueEvent 현재 상태에서 snapshot가져오는 함수

addValueEventListener 하나의 노드 실시간 감시 

addCildEventListener 자식노드까지 실시간 감시

.

.

image

.

.

image
image

.

.

image
image

.

.

image
image

.

.

image
image
image

.

.

firebase realtime database

에는 두가지조건을 동시에 검색하는것이 없다.

image

이런것을 불가능하고

또한 두테이블의 relation 도 만들수 없다.

Convert Firebase Firestore Timestamp to Date (Swift)?

Convert Firebase Firestore Timestamp to Date (Swift)?

ios 앱에서 firebase를 이용해서 notification 작업을 수행하는 경우. 매 기기마다 주어지는 registration id를 이용해서 각각의 기기에 push notification을 보내게된다. 이 id는 기기에서 앱이 시작될때마다 MessagingDelegate의  messaging:didReceiveRegistrationToken이 호출되고 id를 얻을수 있게 된다. 또 새로운 id가 주어진경우에도 이 함수가 호출된다. 사용자가 이미 sign in되어있는 상황에서는 sign in 과정을 거치지 않고 bypass 되므로 이경우 위해 didReceiveRegistrationToken 내에서 registration id을 저장하는 작업을 수행해야 한다.또 sign in 되어있지 않아서 sign in 과정을 거쳐야 하는 경우를 위해서 sign in 과정에서도 registration id를 저장하는 작업을 수행해 주어야 한다. 이때는 

InstanceID.instanceID().instanceID { (result, error) in
 if let error = error {
   print("Error fetching remote instange ID: (error)")
 } else if let result = result {
   print("Remote instance ID token: (result.token)")
   self.instanceIDTokenMessage.text  = "Remote InstanceID token: (result.token)"
 }
}

와 같이 registration id (위의 코드에서 result.token)에 접근가능하다.

ios 앱에서 firebase를 이용해서 notification 작업을 수행하는 경우. 매 기기마다 주어지는 registration id를 이용해서 각각의 기기에 push notification을 보내게된다. 이 id는 기기에서 앱이 시작될때마다 MessagingDelegate의  messaging:didReceiveRegistrationToken이 호출되고 id를 얻을수 있게 된다. 또 새로운 id가 주어진경우에도 이 함수가 호출된다. 사용자가 이미 sign in되어있는 상황에서는 sign in 과정을 거치지 않고 bypass 되므로 이경우 위해 didReceiveRegistrationToken 내에서 registration id을 저장하는 작업을 수행해야 한다.또 sign in 되어있지 않아서 sign in 과정을 거쳐야 하는 경우를 위해서 sign in 과정에서도 registration id를 저장하는 작업을 수행해 주어야 한다. 이때는 

InstanceID.instanceID().instanceID { (result, error) in
 if let error = error {
   print("Error fetching remote instange ID: (error)")
 } else if let result = result {
   print("Remote instance ID token: (result.token)")
   self.instanceIDTokenMessage.text  = "Remote InstanceID token: (result.token)"
 }
}

와 같이 registration id (위의 코드에서 result.token)에 접근가능하다.

firebase functions official examples

https://github.com/firebase/functions-samples

onCreate 을 사용하는 경우 새로 생성된 doc과 context가 되돌려 진다.

doc( DocumentSnapshot ) https://cloud.google.com/nodejs/docs/reference/firestore/0.11.x/DocumentSnapshot

promise error catch 하는 방법

admin.storage().bucket().file('path/to/file').download({
    destination: 'temporary/file/path'
}).then(() => {
    // Change the file and upload it.
}).catch(err => {
    // Handle error(create file) if the file does not exist
})

firestore경로에 있는 문자열을 parameter로 사용하는 방법

exports.observeCreate = functions.firestore.document('/pathOne/{id}/pathTwo/{anotherId}').onCreate((snapshot, context) => {
  console.log(context.params);
  console.log(context.params.id);
});

notification and nested 작업

exports.observeCreate = functions.firestore.document('/pathOne/{id}/pathTwo/{anotherId}').onCreate(event => {
  console.log(event.params);

  //event prints out data but params undefined...
  const data = event.data()

  var id = event.params.id;

  return admin.firestore().collection('path').doc(id).get().then(doc => {
    const data = doc.data();
    var fcmToken = data.fcmToken;

    var message = {
      notification: {
        title: "x",
        body: "x"
      },
      token: fcmToken
    };

    admin.messaging().send(message)
      .then((response) => {
        console.log('Successfully sent message:', response);
        return;
      })
      .catch((error) => {
        console.log('Error sending message:', error);
        return;
      });

      return;
  })
})

device에 notification 보내기

exports.sendNotification = 
functions.firestore.document('Users/{userId}/Notifications/{notificationId}')
.onWrite((c hange, context) =>{

const userId = context.params.userId;
const notificationId = context.params.notificationId;

console.log('The User id is : ', userId);
console.log('The Notification id is : ', notificationId);

// ref to the parent document

return admin.firestore().collection("Users").doc(userId).collection("Token").doc(userId).get().then(queryResult => {
    const tokenId = queryResult.data().deviceToken;

    //const toUser = admin.firestore().collection("Users").doc(userId).collection("Notifications").doc(notificationId).get();

        const notificationContent = {
                notification:{
                    title: "/*App name */",
                    body: "You have a new Comment!",
                    icon: "default",
                    click_action: "/*Package */_TARGET_NOTIFICATION"
            }
        };

        return admin.messaging().sendToDevice(tokenId, notificationContent).then(result => {
            console.log("Notification sent!");
            //admin.firestore().collection("notifications").doc(userEmail).collection("userNotifications").doc(notificationId).delete();
        });

   });

});

onwrite에 반응해서 특정topic으로 구분된 그룹에게 notification 보내기

exports.sendNotification = functions.firestore
.document('chats/{chatID}')
.onWrite((change, context) => {
  // Get an object representing the document
   console.log('chat triggered');
  // perform desired operations ...

    // See documentation on defining a message payload.
    var message = {
      notification: {
        title: 'Hello World!',
        body: 'Hello World!'
      },
      topic: context.params.chatID.   //<- If you are using a CF version under v1.0 don't change here
    };

    // Send a message to devices subscribed to the provided topic.
    return admin.messaging().send(message).  //<- return the resulting Promise
      .then((response) => {
        // Response is a message ID string.
        console.log('Successfully sent message:', response);
        return true;    //<- return a value
      })
      .catch((error) => {
        console.log('Error sending message:', error);
        //return.  <- No need to return here
      });

});

firestore에서 data fetch해서 가져오기 

firestore.collection('products').doc(payment.product).get().then(product => {
    if (!product.exists) {
        console.log('No such product!');
    } else {
        console.log('Document data:', product.data());
    }
})

function을 이용해서 firestore에 document만들기

exports.createProfile = functions.auth.user().onCreate((user) => {

  var userObject = {
     displayName : user.displayName,
     email : user.email,
  };

  return admin.firestore().doc('users/'+user.uid).set(userObject);

});

firebase auth에 새로운 유저가 생성되는 경우에 따른 작업을 만들때

export const accountCreate = functions.auth.user().onCreate(user => {
    console.log(user.data);
    userDoc = {'email' = user.data.email, 
               'displayName' = user.data.displayName}
    admin.firestore().collection('users').doc(user.data.uid)
    .set(userDoc).then(writeResult => {
        console.log('User Created result:', writeResult);
        return;
    }).catch(err => {
       console.log(err);
       return;
    });
});

firebase functions official examples

https://github.com/firebase/functions-samples

onCreate 을 사용하는 경우 새로 생성된 doc과 context가 되돌려 진다.

doc( DocumentSnapshot ) https://cloud.google.com/nodejs/docs/reference/firestore/0.11.x/DocumentSnapshot

promise error catch 하는 방법

admin.storage().bucket().file('path/to/file').download({
    destination: 'temporary/file/path'
}).then(() => {
    // Change the file and upload it.
}).catch(err => {
    // Handle error(create file) if the file does not exist
})

firestore경로에 있는 문자열을 parameter로 사용하는 방법

exports.observeCreate = functions.firestore.document('/pathOne/{id}/pathTwo/{anotherId}').onCreate((snapshot, context) => {
  console.log(context.params);
  console.log(context.params.id);
});

notification and nested 작업

exports.observeCreate = functions.firestore.document('/pathOne/{id}/pathTwo/{anotherId}').onCreate(event => {
  console.log(event.params);

  //event prints out data but params undefined...
  const data = event.data()

  var id = event.params.id;

  return admin.firestore().collection('path').doc(id).get().then(doc => {
    const data = doc.data();
    var fcmToken = data.fcmToken;

    var message = {
      notification: {
        title: "x",
        body: "x"
      },
      token: fcmToken
    };

    admin.messaging().send(message)
      .then((response) => {
        console.log('Successfully sent message:', response);
        return;
      })
      .catch((error) => {
        console.log('Error sending message:', error);
        return;
      });

      return;
  })
})

device에 notification 보내기

exports.sendNotification = 
functions.firestore.document('Users/{userId}/Notifications/{notificationId}')
.onWrite((c hange, context) =>{

const userId = context.params.userId;
const notificationId = context.params.notificationId;

console.log('The User id is : ', userId);
console.log('The Notification id is : ', notificationId);

// ref to the parent document

return admin.firestore().collection("Users").doc(userId).collection("Token").doc(userId).get().then(queryResult => {
    const tokenId = queryResult.data().deviceToken;

    //const toUser = admin.firestore().collection("Users").doc(userId).collection("Notifications").doc(notificationId).get();

        const notificationContent = {
                notification:{
                    title: "/*App name */",
                    body: "You have a new Comment!",
                    icon: "default",
                    click_action: "/*Package */_TARGET_NOTIFICATION"
            }
        };

        return admin.messaging().sendToDevice(tokenId, notificationContent).then(result => {
            console.log("Notification sent!");
            //admin.firestore().collection("notifications").doc(userEmail).collection("userNotifications").doc(notificationId).delete();
        });

   });

});

onwrite에 반응해서 특정topic으로 구분된 그룹에게 notification 보내기

exports.sendNotification = functions.firestore
.document('chats/{chatID}')
.onWrite((change, context) => {
  // Get an object representing the document
   console.log('chat triggered');
  // perform desired operations ...

    // See documentation on defining a message payload.
    var message = {
      notification: {
        title: 'Hello World!',
        body: 'Hello World!'
      },
      topic: context.params.chatID.   //<- If you are using a CF version under v1.0 don't change here
    };

    // Send a message to devices subscribed to the provided topic.
    return admin.messaging().send(message).  //<- return the resulting Promise
      .then((response) => {
        // Response is a message ID string.
        console.log('Successfully sent message:', response);
        return true;    //<- return a value
      })
      .catch((error) => {
        console.log('Error sending message:', error);
        //return.  <- No need to return here
      });

});

firestore에서 data fetch해서 가져오기 

firestore.collection('products').doc(payment.product).get().then(product => {
    if (!product.exists) {
        console.log('No such product!');
    } else {
        console.log('Document data:', product.data());
    }
})

function을 이용해서 firestore에 document만들기

exports.createProfile = functions.auth.user().onCreate((user) => {

  var userObject = {
     displayName : user.displayName,
     email : user.email,
  };

  return admin.firestore().doc('users/'+user.uid).set(userObject);

});

firebase auth에 새로운 유저가 생성되는 경우에 따른 작업을 만들때

export const accountCreate = functions.auth.user().onCreate(user => {
    console.log(user.data);
    userDoc = {'email' = user.data.email, 
               'displayName' = user.data.displayName}
    admin.firestore().collection('users').doc(user.data.uid)
    .set(userDoc).then(writeResult => {
        console.log('User Created result:', writeResult);
        return;
    }).catch(err => {
       console.log(err);
       return;
    });
});