├── .gitignore ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── example │ │ └── database │ │ ├── BaseActivity.java │ │ ├── BasicActivity.java │ │ ├── ChatActivity.java │ │ ├── MainActivity.java │ │ ├── MyApplication.java │ │ ├── NewPostActivity.java │ │ ├── PostDetailActivity.java │ │ ├── SignInActivity.java │ │ ├── SwitchActivity.java │ │ ├── fragment │ │ ├── MyPostsFragment.java │ │ ├── MyTopPostsFragment.java │ │ ├── PostListFragment.java │ │ └── RecentPostsFragment.java │ │ ├── models │ │ ├── Comment.java │ │ ├── FriendlyMessage.java │ │ ├── Post.java │ │ └── User.java │ │ └── viewholder │ │ └── PostViewHolder.java │ └── res │ ├── drawable-hdpi │ ├── ic_action_account_circle_40.png │ ├── ic_image_edit.png │ ├── ic_navigation_check_24.png │ ├── ic_toggle_star_24.png │ └── ic_toggle_star_outline_24.png │ ├── drawable-xhdpi │ ├── ic_action_account_circle_40.png │ ├── ic_image_edit.png │ ├── ic_navigation_check_24.png │ ├── ic_toggle_star_24.png │ └── ic_toggle_star_outline_24.png │ ├── drawable-xxhdpi │ ├── ic_action_account_circle_40.png │ ├── ic_image_edit.png │ ├── ic_navigation_check_24.png │ ├── ic_toggle_star_24.png │ └── ic_toggle_star_outline_24.png │ ├── drawable-xxxhdpi │ ├── ic_action_account_circle_40.png │ ├── ic_image_edit.png │ ├── ic_navigation_check_24.png │ ├── ic_toggle_star_24.png │ ├── ic_toggle_star_outline_24.png │ └── logo.png │ ├── layout │ ├── activity_chat.xml │ ├── activity_main.xml │ ├── activity_new_post.xml │ ├── activity_post_detail.xml │ ├── activity_sample.xml │ ├── activity_sign_in.xml │ ├── activity_switch.xml │ ├── fragment_all_posts.xml │ ├── include_post_author.xml │ ├── include_post_text.xml │ ├── item_comment.xml │ ├── item_message.xml │ └── item_post.xml │ ├── menu │ └── menu_main.xml │ ├── mipmap-hdpi │ └── ic_launcher.png │ ├── mipmap-mdpi │ └── ic_launcher.png │ ├── mipmap-xhdpi │ └── ic_launcher.png │ ├── mipmap-xxhdpi │ └── ic_launcher.png │ ├── mipmap-xxxhdpi │ └── ic_launcher.png │ ├── values-w820dp │ └── dimens.xml │ └── values │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | #built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # files for the dex VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # generated files 12 | bin/ 13 | gen/ 14 | out/ 15 | 16 | # Local configuration file (sdk path, etc) 17 | local.properties 18 | 19 | # Windows thumbnail db 20 | Thumbs.db 21 | 22 | # OSX files 23 | .DS_Store 24 | 25 | # Log Files 26 | *.log 27 | 28 | # Android Studio 29 | *.iml 30 | .gradle/ 31 | build/ 32 | captures/ 33 | .navigation/ 34 | 35 | # Intellij IDEA 36 | .idea/ 37 | 38 | # Eclipse project files 39 | .classpath 40 | .project 41 | 42 | # Proguard folder generated by Eclipse 43 | proguard/ 44 | 45 | # Eclipse Metadata 46 | .metadata/ 47 | 48 | # Keystore files 49 | *.jks 50 | 51 | .externalNativeBuild 52 | 53 | google-services.json 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Firebase Realtime Database 2 | Firebase Realtime Database code guideline for Android developer 3 | 4 | ## Prerequisites 5 | * Supported Android 4.1 or newer 6 | * Android Studio 3.3.2 or higher 7 | * google-services.json in app-level folder 8 | 9 | ## Features 10 | * No Authentication usage 11 | * Authentication usage 12 | * Blog app demo 13 | * Chat app demo 14 | * Write Data 15 | * Read Data 16 | * Enabling Offline Capabilities 17 | 18 | ## Screenshots 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | ## Blog 29 | [Firebase Realtime Database](https://medium.com/@jirawatee/%E0%B8%A3%E0%B8%B9%E0%B9%89%E0%B8%88%E0%B8%B1%E0%B8%81-firebase-authentication-%E0%B8%95%E0%B8%B1%E0%B9%89%E0%B8%87%E0%B9%81%E0%B8%95%E0%B9%88-zero-%E0%B8%88%E0%B8%99%E0%B9%80%E0%B8%9B%E0%B9%87%E0%B8%99-hero-7dd5839d3588) 30 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion compileAndTargetSdk 5 | buildToolsVersion '30.0.3' 6 | 7 | defaultConfig { 8 | applicationId "com.example.database" 9 | minSdkVersion 21 10 | targetSdkVersion compileAndTargetSdk 11 | versionCode 1 12 | versionName '1.0' 13 | resConfigs ('en', 'xxxhdpi') 14 | ndk { 15 | abiFilters "x86", "x86_64", "arm64-v8a", "armeabi-v7a" 16 | } 17 | } 18 | 19 | buildTypes { 20 | release { 21 | minifyEnabled true 22 | shrinkResources true 23 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 24 | } 25 | debug { 26 | splits.abi.enable = false 27 | splits.density.enable = false 28 | aaptOptions.cruncherEnabled = false 29 | } 30 | } 31 | 32 | dexOptions { 33 | preDexLibraries true 34 | maxProcessCount 8 35 | } 36 | } 37 | 38 | dependencies { 39 | implementation 'androidx.appcompat:appcompat:1.2.0' 40 | implementation 'androidx.recyclerview:recyclerview:1.2.0' 41 | implementation 'androidx.cardview:cardview:1.0.0' 42 | implementation 'com.google.android.material:material:1.3.0' 43 | implementation 'com.firebaseui:firebase-ui-database:7.1.1' 44 | implementation "com.google.firebase:firebase-analytics:18.0.3" 45 | implementation "com.google.firebase:firebase-database:19.7.0" 46 | implementation "com.google.firebase:firebase-auth:20.0.4" 47 | } 48 | 49 | apply plugin: 'com.google.gms.google-services' -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add this global rule 2 | -keepattributes Signature 3 | -keepattributes *Annotation* 4 | -keepattributes EnclosingMethod 5 | -keepattributes InnerClasses 6 | 7 | -keep class com.example.fdatabase.viewholder.** {*;} 8 | -keepclassmembers class com.example.fdatabase.models.** {*;} 9 | 10 | -assumenosideeffects class android.util.Log { 11 | public static boolean isLoggable(java.lang.String, int); 12 | public static int v(...); 13 | public static int i(...); 14 | public static int w(...); 15 | public static int d(...); 16 | public static int e(...); 17 | public static int wtf(...); 18 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/database/BaseActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.database; 2 | 3 | import android.app.ProgressDialog; 4 | import androidx.appcompat.app.AppCompatActivity; 5 | 6 | import com.google.firebase.auth.FirebaseAuth; 7 | 8 | 9 | public class BaseActivity extends AppCompatActivity { 10 | private ProgressDialog mProgressDialog; 11 | 12 | public void showProgressDialog() { 13 | if (mProgressDialog == null) { 14 | mProgressDialog = new ProgressDialog(this); 15 | mProgressDialog.setCancelable(false); 16 | mProgressDialog.setMessage("Loading..."); 17 | } 18 | mProgressDialog.show(); 19 | } 20 | 21 | public void hideProgressDialog() { 22 | if (mProgressDialog != null && mProgressDialog.isShowing()) { 23 | mProgressDialog.dismiss(); 24 | } 25 | } 26 | 27 | public String getUid() { 28 | return FirebaseAuth.getInstance().getCurrentUser().getUid(); 29 | } 30 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/database/BasicActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.database; 2 | 3 | import android.app.Dialog; 4 | import android.os.Bundle; 5 | import androidx.appcompat.app.AppCompatActivity; 6 | import android.text.TextUtils; 7 | import android.text.format.DateUtils; 8 | import android.text.method.ScrollingMovementMethod; 9 | import android.view.View; 10 | import android.widget.Button; 11 | import android.widget.EditText; 12 | import android.widget.LinearLayout; 13 | import android.widget.ProgressBar; 14 | import android.widget.TextView; 15 | 16 | import com.example.database.models.FriendlyMessage; 17 | import com.google.firebase.database.DataSnapshot; 18 | import com.google.firebase.database.DatabaseError; 19 | import com.google.firebase.database.DatabaseReference; 20 | import com.google.firebase.database.FirebaseDatabase; 21 | import com.google.firebase.database.ValueEventListener; 22 | 23 | import java.util.HashMap; 24 | import java.util.Map; 25 | 26 | import static com.example.database.R.string.username; 27 | 28 | public class BasicActivity extends AppCompatActivity { 29 | private static final String CHILD_USERS = "chat-users"; 30 | private static final String CHILD_MESSAGES = "chat"; 31 | private static final String UID = "id-12345"; 32 | private Button mButtonSet, mButtonPush, mButtonUpdateChildren, mButtonRemove; 33 | private DatabaseReference mRootRef, mUsersRef, mMessageRef; 34 | private Dialog mDialog; 35 | private EditText mEdtUsername, mEdtMessage; 36 | private String mUsername; 37 | private TextView mTextView; 38 | private ValueEventListener mValueEventListener; 39 | 40 | @Override 41 | protected void onCreate(Bundle savedInstanceState) { 42 | super.onCreate(savedInstanceState); 43 | setContentView(R.layout.activity_sample); 44 | initWidget(); 45 | 46 | FirebaseDatabase firebaseDatabase = FirebaseDatabase.getInstance(); 47 | 48 | mRootRef = firebaseDatabase.getReference(); 49 | mUsersRef = mRootRef.child(CHILD_USERS); 50 | mMessageRef = mRootRef.child(CHILD_MESSAGES); 51 | 52 | setEventListener(); 53 | } 54 | 55 | @Override 56 | protected void onStart() { 57 | super.onStart(); 58 | mDialog.show(); 59 | mValueEventListener = new ValueEventListener() { 60 | @Override 61 | public void onDataChange(DataSnapshot dataSnapshot) { 62 | mDialog.dismiss(); 63 | mUsername = dataSnapshot.child(CHILD_USERS).child(UID).getValue(String.class); 64 | 65 | mTextView.setText(getString(username, mUsername)); 66 | if (TextUtils.isEmpty(mUsername)) { 67 | mButtonPush.setEnabled(false); 68 | mButtonUpdateChildren.setEnabled(false); 69 | } else { 70 | mButtonPush.setEnabled(true); 71 | mButtonUpdateChildren.setEnabled(true); 72 | } 73 | Iterable children = dataSnapshot.child(CHILD_MESSAGES).getChildren(); 74 | while(children.iterator().hasNext()){ 75 | String key= children.iterator().next().getKey(); 76 | FriendlyMessage friendlyMessage = dataSnapshot.child(CHILD_MESSAGES).child(key).getValue(FriendlyMessage.class); 77 | 78 | long now = System.currentTimeMillis(); 79 | long past = now - (60 * 60 * 24 * 45 * 1000L); 80 | String x = DateUtils.getRelativeTimeSpanString(past, now, DateUtils.MINUTE_IN_MILLIS).toString(); 81 | 82 | mTextView.append("username: " + friendlyMessage.getUsername() + " | "); 83 | mTextView.append("text: " + friendlyMessage.getText() + " (" + x + ")" + "\n"); 84 | } 85 | } 86 | 87 | @Override 88 | public void onCancelled(DatabaseError databaseError) { 89 | mDialog.dismiss(); 90 | mTextView.setText(getString(R.string.fail_read, databaseError.getMessage())); 91 | } 92 | }; 93 | mRootRef.addValueEventListener(mValueEventListener); 94 | } 95 | 96 | @Override 97 | protected void onStop() { 98 | super.onStop(); 99 | if (mValueEventListener != null) { 100 | mRootRef.removeEventListener(mValueEventListener); 101 | } 102 | } 103 | 104 | private void initWidget() { 105 | mTextView = findViewById(R.id.txt_result); 106 | mTextView.setMovementMethod(new ScrollingMovementMethod()); 107 | mEdtUsername = findViewById(R.id.edt_username); 108 | mEdtMessage = findViewById(R.id.edt_message); 109 | mButtonSet = findViewById(R.id.btn_set); 110 | mButtonPush = findViewById(R.id.btn_push); 111 | mButtonUpdateChildren = findViewById(R.id.btn_update); 112 | mButtonRemove = findViewById(R.id.btn_remove); 113 | mDialog = new Dialog(this, R.style.NewDialog); 114 | mDialog.addContentView( 115 | new ProgressBar(this), 116 | new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT) 117 | ); 118 | mDialog.setCancelable(true); 119 | } 120 | 121 | private void setEventListener() { 122 | mButtonSet.setOnClickListener(new View.OnClickListener() { 123 | @Override 124 | public void onClick(View view) { 125 | mUsername = mEdtUsername.getText().toString().trim(); 126 | if (TextUtils.isEmpty(mUsername)) { 127 | mEdtUsername.setError(getString(R.string.required)); 128 | } else { 129 | mUsersRef.child(UID).setValue(mUsername); 130 | mEdtUsername.setError(null); 131 | mEdtUsername.setText(null); 132 | } 133 | } 134 | }); 135 | mButtonPush.setOnClickListener(new View.OnClickListener() { 136 | @Override 137 | public void onClick(View view) { 138 | String message = mEdtMessage.getText().toString().trim(); 139 | if (TextUtils.isEmpty(message)) { 140 | mEdtMessage.setError(getString(R.string.required)); 141 | } else { 142 | FriendlyMessage friendlyMessage = new FriendlyMessage(message, mUsername); 143 | mMessageRef.push().setValue(friendlyMessage); 144 | mEdtMessage.setError(null); 145 | mEdtMessage.setText(null); 146 | } 147 | } 148 | }); 149 | mButtonUpdateChildren.setOnClickListener(new View.OnClickListener() { 150 | @Override 151 | public void onClick(View view) { 152 | String key = mMessageRef.push().getKey(); 153 | String message = mEdtMessage.getText().toString().trim(); 154 | if (TextUtils.isEmpty(message)) { 155 | mEdtMessage.setError(getString(R.string.required)); 156 | } else { 157 | HashMap postValues = new HashMap<>(); 158 | postValues.put("username", mUsername); 159 | postValues.put("text", message); 160 | 161 | Map childUpdates = new HashMap<>(); 162 | childUpdates.put("/chat/" + key, postValues); 163 | childUpdates.put("/chat-each-users/" + mUsername + "/" + key, postValues); 164 | mRootRef.updateChildren(childUpdates); 165 | 166 | mEdtMessage.setError(null); 167 | mEdtMessage.setText(null); 168 | } 169 | } 170 | }); 171 | mButtonRemove.setOnClickListener(new View.OnClickListener() { 172 | @Override 173 | public void onClick(View view) { 174 | mMessageRef.removeValue(); 175 | } 176 | }); 177 | } 178 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/database/ChatActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.database; 2 | 3 | import android.os.Bundle; 4 | import androidx.appcompat.app.AppCompatActivity; 5 | import androidx.recyclerview.widget.LinearLayoutManager; 6 | import androidx.recyclerview.widget.RecyclerView; 7 | import android.text.Editable; 8 | import android.text.TextWatcher; 9 | import android.view.Gravity; 10 | import android.view.LayoutInflater; 11 | import android.view.View; 12 | import android.view.ViewGroup; 13 | import android.widget.Button; 14 | import android.widget.EditText; 15 | import android.widget.LinearLayout; 16 | import android.widget.TextView; 17 | 18 | import com.example.database.models.FriendlyMessage; 19 | import com.example.database.models.User; 20 | import com.firebase.ui.database.FirebaseRecyclerAdapter; 21 | import com.firebase.ui.database.FirebaseRecyclerOptions; 22 | import com.google.firebase.auth.FirebaseAuth; 23 | import com.google.firebase.auth.FirebaseUser; 24 | import com.google.firebase.database.DataSnapshot; 25 | import com.google.firebase.database.DatabaseError; 26 | import com.google.firebase.database.DatabaseReference; 27 | import com.google.firebase.database.FirebaseDatabase; 28 | import com.google.firebase.database.Query; 29 | import com.google.firebase.database.ValueEventListener; 30 | 31 | public class ChatActivity extends AppCompatActivity { 32 | public static final String MESSAGES_CHILD = "chat"; 33 | 34 | private DatabaseReference mFirebaseDatabaseReference; 35 | private FirebaseRecyclerAdapter mFirebaseAdapter; 36 | 37 | private Button mSendButton; 38 | private RecyclerView mMessageRecyclerView; 39 | private LinearLayoutManager mLinearLayoutManager; 40 | private EditText mMessageEditText; 41 | private String mEmail = "Anonymous"; 42 | 43 | @Override 44 | protected void onCreate(Bundle savedInstanceState) { 45 | super.onCreate(savedInstanceState); 46 | setContentView(R.layout.activity_chat); 47 | 48 | mMessageEditText = findViewById(R.id.messageEditText); 49 | mMessageRecyclerView = findViewById(R.id.messageRecyclerView); 50 | mLinearLayoutManager = new LinearLayoutManager(this); 51 | mLinearLayoutManager.setStackFromEnd(true); 52 | 53 | // Initialize Firebase Auth 54 | FirebaseAuth mFirebaseAuth = FirebaseAuth.getInstance(); 55 | FirebaseUser mFirebaseUser = mFirebaseAuth.getCurrentUser(); 56 | mFirebaseDatabaseReference = FirebaseDatabase.getInstance().getReference(); 57 | mFirebaseDatabaseReference.child("users").child(mFirebaseUser.getUid()).addValueEventListener(new ValueEventListener() { 58 | @Override 59 | public void onDataChange(DataSnapshot dataSnapshot) { 60 | User user = dataSnapshot.getValue(User.class); 61 | mEmail = user.email; 62 | } 63 | 64 | @Override 65 | public void onCancelled(DatabaseError databaseError) {} 66 | }); 67 | 68 | Query query = mFirebaseDatabaseReference.child(MESSAGES_CHILD); 69 | FirebaseRecyclerOptions options = new FirebaseRecyclerOptions.Builder() 70 | .setQuery(query, FriendlyMessage.class) 71 | .build(); 72 | 73 | mFirebaseAdapter = new FirebaseRecyclerAdapter(options) { 74 | @Override 75 | protected void onBindViewHolder(MessageViewHolder viewHolder, int position, FriendlyMessage friendlyMessage) { 76 | if (friendlyMessage.getUsername().equals(mEmail)) { 77 | viewHolder.row.setGravity(Gravity.END); 78 | } else { 79 | viewHolder.row.setGravity(Gravity.START); 80 | } 81 | viewHolder.messageTextView.setText(friendlyMessage.getText()); 82 | viewHolder.messengerTextView.setText(friendlyMessage.getUsername()); 83 | } 84 | 85 | @Override 86 | public MessageViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) { 87 | LayoutInflater inflater = LayoutInflater.from(viewGroup.getContext()); 88 | return new MessageViewHolder(inflater.inflate(R.layout.item_message, viewGroup, false)); 89 | } 90 | }; 91 | 92 | mFirebaseAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() { 93 | @Override 94 | public void onItemRangeInserted(int positionStart, int itemCount) { 95 | super.onItemRangeInserted(positionStart, itemCount); 96 | int friendlyMessageCount = mFirebaseAdapter.getItemCount(); 97 | int lastVisiblePosition = mLinearLayoutManager.findLastCompletelyVisibleItemPosition(); 98 | // If the recycler view is initially being loaded or the user is at the bottom of the list, scroll 99 | // to the bottom of the list to show the newly added message. 100 | if (lastVisiblePosition == -1 || (positionStart >= (friendlyMessageCount - 1) && lastVisiblePosition == (positionStart - 1))) { 101 | mMessageRecyclerView.scrollToPosition(positionStart); 102 | } 103 | } 104 | }); 105 | mMessageRecyclerView.setLayoutManager(mLinearLayoutManager); 106 | mMessageRecyclerView.setAdapter(mFirebaseAdapter); 107 | 108 | mMessageEditText.addTextChangedListener(new TextWatcher() { 109 | @Override 110 | public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { 111 | } 112 | 113 | @Override 114 | public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { 115 | if (charSequence.toString().trim().length() > 0) { 116 | mSendButton.setEnabled(true); 117 | } else { 118 | mSendButton.setEnabled(false); 119 | } 120 | } 121 | 122 | @Override 123 | public void afterTextChanged(Editable editable) { 124 | } 125 | }); 126 | 127 | mSendButton = findViewById(R.id.sendButton); 128 | mSendButton.setOnClickListener(new View.OnClickListener() { 129 | @Override 130 | public void onClick(View view) { 131 | FriendlyMessage friendlyMessage = new FriendlyMessage(mMessageEditText.getText().toString(), mEmail); 132 | mFirebaseDatabaseReference.child(MESSAGES_CHILD).push().setValue(friendlyMessage); 133 | mMessageEditText.setText(""); 134 | } 135 | }); 136 | } 137 | 138 | @Override 139 | protected void onStart() { 140 | super.onStart(); 141 | if (mFirebaseAdapter != null) { 142 | mFirebaseAdapter.startListening(); 143 | } 144 | } 145 | 146 | @Override 147 | protected void onStop() { 148 | super.onStop(); 149 | if (mFirebaseAdapter != null) { 150 | mFirebaseAdapter.stopListening(); 151 | } 152 | } 153 | 154 | public static class MessageViewHolder extends RecyclerView.ViewHolder { 155 | LinearLayout row; 156 | TextView messageTextView; 157 | TextView messengerTextView; 158 | 159 | MessageViewHolder(View v) { 160 | super(v); 161 | row = itemView.findViewById(R.id.row); 162 | messageTextView = itemView.findViewById(R.id.messageTextView); 163 | messengerTextView = itemView.findViewById(R.id.messengerTextView); 164 | } 165 | } 166 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/database/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.database; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import com.google.android.material.tabs.TabLayout; 6 | import androidx.fragment.app.Fragment; 7 | import androidx.fragment.app.FragmentPagerAdapter; 8 | import androidx.viewpager.widget.ViewPager; 9 | import androidx.appcompat.app.AppCompatActivity; 10 | import android.view.Menu; 11 | import android.view.MenuItem; 12 | import android.view.View; 13 | 14 | import com.example.database.fragment.MyPostsFragment; 15 | import com.example.database.fragment.MyTopPostsFragment; 16 | import com.example.database.fragment.RecentPostsFragment; 17 | import com.google.firebase.auth.FirebaseAuth; 18 | 19 | public class MainActivity extends AppCompatActivity { 20 | @Override 21 | protected void onCreate(Bundle savedInstanceState) { 22 | super.onCreate(savedInstanceState); 23 | setContentView(R.layout.activity_main); 24 | 25 | FragmentPagerAdapter mPagerAdapter = new FragmentPagerAdapter(getSupportFragmentManager()) { 26 | private final Fragment[] mFragments = new Fragment[] { 27 | new RecentPostsFragment(), 28 | new MyPostsFragment(), 29 | new MyTopPostsFragment(), 30 | }; 31 | 32 | @Override 33 | public Fragment getItem(int position) { 34 | return mFragments[position]; 35 | } 36 | @Override 37 | public int getCount() { 38 | return mFragments.length; 39 | } 40 | @Override 41 | public CharSequence getPageTitle(int position) { 42 | return getResources().getStringArray(R.array.headings)[position]; 43 | } 44 | }; 45 | 46 | ViewPager mViewPager = findViewById(R.id.container); 47 | mViewPager.setAdapter(mPagerAdapter); 48 | 49 | TabLayout tabLayout = findViewById(R.id.tabs); 50 | tabLayout.setupWithViewPager(mViewPager); 51 | 52 | findViewById(R.id.fab_new_post).setOnClickListener(new View.OnClickListener() { 53 | @Override 54 | public void onClick(View v) { 55 | startActivity(new Intent(MainActivity.this, NewPostActivity.class)); 56 | } 57 | }); 58 | } 59 | 60 | @Override 61 | public boolean onCreateOptionsMenu(Menu menu) { 62 | getMenuInflater().inflate(R.menu.menu_main, menu); 63 | return true; 64 | } 65 | 66 | @Override 67 | public boolean onOptionsItemSelected(MenuItem item) { 68 | switch(item.getItemId()) { 69 | case R.id.action_chat: 70 | startActivity(new Intent(this, ChatActivity.class)); 71 | return true; 72 | case R.id.action_logout: 73 | FirebaseAuth.getInstance().signOut(); 74 | startActivity(new Intent(this, SignInActivity.class)); 75 | finish(); 76 | return true; 77 | default: 78 | return super.onOptionsItemSelected(item); 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/database/MyApplication.java: -------------------------------------------------------------------------------- 1 | package com.example.database; 2 | 3 | import android.app.Application; 4 | 5 | import com.google.firebase.database.FirebaseDatabase; 6 | 7 | public class MyApplication extends Application{ 8 | @Override 9 | public void onCreate() { 10 | super.onCreate(); 11 | FirebaseDatabase.getInstance().setPersistenceEnabled(true); 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/database/NewPostActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.database; 2 | 3 | import android.os.Bundle; 4 | import com.google.android.material.floatingactionbutton.FloatingActionButton; 5 | import android.text.TextUtils; 6 | import android.view.View; 7 | import android.widget.EditText; 8 | import android.widget.Toast; 9 | 10 | import com.example.database.models.Post; 11 | import com.example.database.models.User; 12 | import com.google.firebase.database.DataSnapshot; 13 | import com.google.firebase.database.DatabaseError; 14 | import com.google.firebase.database.DatabaseReference; 15 | import com.google.firebase.database.FirebaseDatabase; 16 | import com.google.firebase.database.ValueEventListener; 17 | 18 | import java.util.HashMap; 19 | import java.util.Map; 20 | 21 | public class NewPostActivity extends BaseActivity { 22 | private DatabaseReference mDatabase; 23 | private EditText mTitleField, mBodyField; 24 | private FloatingActionButton mSubmitButton; 25 | 26 | @Override 27 | protected void onCreate(Bundle savedInstanceState) { 28 | super.onCreate(savedInstanceState); 29 | setContentView(R.layout.activity_new_post); 30 | mTitleField = findViewById(R.id.field_title); 31 | mBodyField = findViewById(R.id.field_body); 32 | mSubmitButton = findViewById(R.id.fab_submit_post); 33 | 34 | mDatabase = FirebaseDatabase.getInstance().getReference(); 35 | 36 | mSubmitButton.setOnClickListener(new View.OnClickListener() { 37 | @Override 38 | public void onClick(View v) { 39 | submitPost(); 40 | } 41 | }); 42 | } 43 | 44 | private boolean validateForm(String title, String body) { 45 | if (TextUtils.isEmpty(title)) { 46 | mTitleField.setError(getString(R.string.required)); 47 | return false; 48 | } else if (TextUtils.isEmpty(body)) { 49 | mBodyField.setError(getString(R.string.required)); 50 | return false; 51 | } else { 52 | mTitleField.setError(null); 53 | mBodyField.setError(null); 54 | return true; 55 | } 56 | } 57 | 58 | private void submitPost() { 59 | final String title = mTitleField.getText().toString().trim(); 60 | final String body = mBodyField.getText().toString().trim(); 61 | final String userId = getUid(); 62 | 63 | if (validateForm(title, body)) { 64 | // Disable button so there are no multi-posts 65 | setEditingEnabled(false); 66 | mDatabase.child("users").child(userId).addListenerForSingleValueEvent(new ValueEventListener() { 67 | @Override 68 | public void onDataChange(DataSnapshot dataSnapshot) { 69 | User user = dataSnapshot.getValue(User.class); 70 | if (user == null) { 71 | Toast.makeText(NewPostActivity.this, "Error: could not fetch user.", Toast.LENGTH_LONG).show(); 72 | } else { 73 | writeNewPost(userId, user.username, title, body); 74 | } 75 | setEditingEnabled(true); 76 | finish(); 77 | } 78 | 79 | @Override 80 | public void onCancelled(DatabaseError databaseError) { 81 | setEditingEnabled(true); 82 | Toast.makeText(NewPostActivity.this, "onCancelled: " + databaseError.getMessage(), Toast.LENGTH_LONG).show(); 83 | } 84 | }); 85 | } 86 | } 87 | 88 | private void setEditingEnabled(boolean enabled) { 89 | mTitleField.setEnabled(enabled); 90 | mBodyField.setEnabled(enabled); 91 | if (enabled) { 92 | mSubmitButton.setVisibility(View.VISIBLE); 93 | } else { 94 | mSubmitButton.setVisibility(View.GONE); 95 | } 96 | } 97 | 98 | private void writeNewPost(String userId, String username, String title, String body) { 99 | // Create new post at /user-posts/$userid/$postid 100 | // and at /posts/$postid simultaneously 101 | String key = mDatabase.child("posts").push().getKey(); 102 | Post post = new Post(userId, username, title, body); 103 | Map postValues = post.toMap(); 104 | 105 | Map childUpdates = new HashMap<>(); 106 | childUpdates.put("/posts/" + key, postValues); 107 | childUpdates.put("/user-posts/" + userId + "/" + key, postValues); 108 | 109 | mDatabase.updateChildren(childUpdates); 110 | } 111 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/database/PostDetailActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.database; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | import androidx.recyclerview.widget.LinearLayoutManager; 6 | import androidx.recyclerview.widget.RecyclerView; 7 | import android.util.Log; 8 | import android.view.LayoutInflater; 9 | import android.view.View; 10 | import android.view.ViewGroup; 11 | import android.widget.Button; 12 | import android.widget.EditText; 13 | import android.widget.TextView; 14 | import android.widget.Toast; 15 | 16 | import com.example.database.models.Comment; 17 | import com.example.database.models.Post; 18 | import com.example.database.models.User; 19 | import com.google.firebase.database.ChildEventListener; 20 | import com.google.firebase.database.DataSnapshot; 21 | import com.google.firebase.database.DatabaseError; 22 | import com.google.firebase.database.DatabaseReference; 23 | import com.google.firebase.database.FirebaseDatabase; 24 | import com.google.firebase.database.ValueEventListener; 25 | 26 | import java.util.ArrayList; 27 | import java.util.List; 28 | 29 | public class PostDetailActivity extends BaseActivity implements View.OnClickListener { 30 | private static final String TAG = "PostDetailActivity"; 31 | public static final String EXTRA_POST_KEY = "post_key"; 32 | private DatabaseReference mPostReference, mCommentsReference; 33 | private ValueEventListener mPostListener; 34 | private CommentAdapter mAdapter; 35 | private TextView mAuthorView, mTitleView, mBodyView; 36 | private EditText mCommentField; 37 | private RecyclerView mCommentsRecycler; 38 | 39 | @Override 40 | protected void onCreate(Bundle savedInstanceState) { 41 | super.onCreate(savedInstanceState); 42 | setContentView(R.layout.activity_post_detail); 43 | mAuthorView = findViewById(R.id.post_author); 44 | mTitleView = findViewById(R.id.post_title); 45 | mBodyView = findViewById(R.id.post_body); 46 | mCommentField = findViewById(R.id.field_comment_text); 47 | 48 | mCommentsRecycler = findViewById(R.id.recycler_comments); 49 | mCommentsRecycler.setLayoutManager(new LinearLayoutManager(this)); 50 | 51 | Button mCommentButton = findViewById(R.id.button_post_comment); 52 | mCommentButton.setOnClickListener(this); 53 | 54 | // Get post key from intent 55 | String mPostKey = getIntent().getStringExtra(EXTRA_POST_KEY); 56 | if (mPostKey == null) { 57 | throw new IllegalArgumentException("Must pass EXTRA_POST_KEY"); 58 | } 59 | 60 | // Initialize Database 61 | mPostReference = FirebaseDatabase.getInstance().getReference().child("posts").child(mPostKey); 62 | mCommentsReference = FirebaseDatabase.getInstance().getReference().child("post-comments").child(mPostKey); 63 | } 64 | 65 | @Override 66 | public void onStart() { 67 | super.onStart(); 68 | 69 | // Add value event listener to the post 70 | ValueEventListener postListener = new ValueEventListener() { 71 | @Override 72 | public void onDataChange(DataSnapshot dataSnapshot) { 73 | // Get Post object and use the values to update the UI 74 | Post post = dataSnapshot.getValue(Post.class); 75 | 76 | mAuthorView.setText(post.author); 77 | mTitleView.setText(post.title); 78 | mBodyView.setText(post.body); 79 | } 80 | 81 | @Override 82 | public void onCancelled(DatabaseError databaseError) { 83 | // Getting Post failed, log a message 84 | Log.w(TAG, "loadPost:onCancelled", databaseError.toException()); 85 | Toast.makeText(PostDetailActivity.this, "Failed to load post.", Toast.LENGTH_SHORT).show(); 86 | } 87 | }; 88 | mPostReference.addValueEventListener(postListener); 89 | 90 | // Keep copy of post listener so we can remove it when app stops 91 | mPostListener = postListener; 92 | 93 | // Listen for comments 94 | mAdapter = new CommentAdapter(this, mCommentsReference); 95 | mCommentsRecycler.setAdapter(mAdapter); 96 | } 97 | 98 | @Override 99 | public void onStop() { 100 | super.onStop(); 101 | if (mPostListener != null) { 102 | mPostReference.removeEventListener(mPostListener); 103 | } 104 | mAdapter.cleanupListener(); 105 | } 106 | 107 | @Override 108 | public void onClick(View v) { 109 | switch (v.getId()) { 110 | case R.id.button_post_comment: 111 | postComment(); 112 | break; 113 | } 114 | } 115 | 116 | private void postComment() { 117 | final String uid = getUid(); 118 | FirebaseDatabase.getInstance().getReference().child("users").child(uid).addListenerForSingleValueEvent(new ValueEventListener() { 119 | @Override 120 | public void onDataChange(DataSnapshot dataSnapshot) { 121 | // Get user information 122 | User user = dataSnapshot.getValue(User.class); 123 | String authorName = user.username; 124 | 125 | // Create new comment object 126 | String commentText = mCommentField.getText().toString().trim(); 127 | Comment comment = new Comment(uid, authorName, commentText); 128 | 129 | // Push the comment, it will appear in the list 130 | mCommentsReference.push().setValue(comment); 131 | 132 | // Clear the field 133 | mCommentField.setText(null); 134 | } 135 | 136 | @Override 137 | public void onCancelled(DatabaseError databaseError) { 138 | Toast.makeText(PostDetailActivity.this, "onCancelled: " + databaseError.getMessage(), Toast.LENGTH_LONG).show(); 139 | } 140 | }); 141 | } 142 | 143 | private static class CommentViewHolder extends RecyclerView.ViewHolder { 144 | TextView authorView; 145 | TextView bodyView; 146 | CommentViewHolder(View itemView) { 147 | super(itemView); 148 | authorView = itemView.findViewById(R.id.comment_author); 149 | bodyView = itemView.findViewById(R.id.comment_body); 150 | } 151 | } 152 | 153 | private static class CommentAdapter extends RecyclerView.Adapter { 154 | private Context mContext; 155 | private DatabaseReference mDatabaseReference; 156 | private ChildEventListener mChildEventListener; 157 | private List mCommentIds = new ArrayList<>(); 158 | private List mComments = new ArrayList<>(); 159 | 160 | CommentAdapter(final Context context, DatabaseReference ref) { 161 | mContext = context; 162 | mDatabaseReference = ref; 163 | 164 | // Create child event listener 165 | ChildEventListener childEventListener = new ChildEventListener() { 166 | @Override 167 | public void onChildAdded(DataSnapshot dataSnapshot, String previousChildName) { 168 | Log.d(TAG, "onChildAdded:" + dataSnapshot.getKey()); 169 | 170 | // A new comment has been added, add it to the displayed list 171 | Comment comment = dataSnapshot.getValue(Comment.class); 172 | 173 | // Update RecyclerView 174 | mCommentIds.add(dataSnapshot.getKey()); 175 | mComments.add(comment); 176 | notifyItemInserted(mComments.size() - 1); 177 | } 178 | 179 | @Override 180 | public void onChildChanged(DataSnapshot dataSnapshot, String previousChildName) { 181 | Log.d(TAG, "onChildChanged:" + dataSnapshot.getKey()); 182 | 183 | // A comment has changed, use the key to determine if we are displaying this 184 | // comment and if so displayed the changed comment. 185 | Comment newComment = dataSnapshot.getValue(Comment.class); 186 | String commentKey = dataSnapshot.getKey(); 187 | 188 | int commentIndex = mCommentIds.indexOf(commentKey); 189 | if (commentIndex > -1) { 190 | // Replace with the new data 191 | mComments.set(commentIndex, newComment); 192 | 193 | // Update the RecyclerView 194 | notifyItemChanged(commentIndex); 195 | } else { 196 | Log.w(TAG, "onChildChanged:unknown_child:" + commentKey); 197 | } 198 | } 199 | 200 | @Override 201 | public void onChildRemoved(DataSnapshot dataSnapshot) { 202 | Log.d(TAG, "onChildRemoved:" + dataSnapshot.getKey()); 203 | 204 | // A comment has changed, use the key to determine if we are displaying this 205 | // comment and if so remove it. 206 | String commentKey = dataSnapshot.getKey(); 207 | 208 | int commentIndex = mCommentIds.indexOf(commentKey); 209 | if (commentIndex > -1) { 210 | // Remove data from the list 211 | mCommentIds.remove(commentIndex); 212 | mComments.remove(commentIndex); 213 | 214 | // Update the RecyclerView 215 | notifyItemRemoved(commentIndex); 216 | } else { 217 | Log.w(TAG, "onChildRemoved:unknown_child:" + commentKey); 218 | } 219 | } 220 | 221 | @Override 222 | public void onChildMoved(DataSnapshot dataSnapshot, String previousChildName) { 223 | Log.d(TAG, "onChildMoved:" + dataSnapshot.getKey()); 224 | 225 | // A comment has changed position, use the key to determine if we are 226 | // displaying this comment and if so move it. 227 | //Comment movedComment = dataSnapshot.getValue(Comment.class); 228 | //String commentKey = dataSnapshot.getKey(); 229 | } 230 | 231 | @Override 232 | public void onCancelled(DatabaseError databaseError) { 233 | Log.w(TAG, "postComments:onCancelled", databaseError.toException()); 234 | Toast.makeText(mContext, "Failed to load comments.", Toast.LENGTH_SHORT).show(); 235 | } 236 | }; 237 | ref.addChildEventListener(childEventListener); 238 | 239 | // Store reference to listener so it can be removed on app stop 240 | mChildEventListener = childEventListener; 241 | } 242 | 243 | @Override 244 | public CommentViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 245 | LayoutInflater inflater = LayoutInflater.from(mContext); 246 | View view = inflater.inflate(R.layout.item_comment, parent, false); 247 | return new CommentViewHolder(view); 248 | } 249 | 250 | @Override 251 | public void onBindViewHolder(CommentViewHolder holder, int position) { 252 | Comment comment = mComments.get(position); 253 | holder.authorView.setText(comment.author); 254 | holder.bodyView.setText(comment.text); 255 | } 256 | 257 | @Override 258 | public int getItemCount() { 259 | return mComments.size(); 260 | } 261 | 262 | void cleanupListener() { 263 | if (mChildEventListener != null) { 264 | mDatabaseReference.removeEventListener(mChildEventListener); 265 | } 266 | } 267 | } 268 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/database/SignInActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.database; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import androidx.annotation.NonNull; 6 | import android.text.TextUtils; 7 | import android.view.View; 8 | import android.widget.EditText; 9 | import android.widget.Toast; 10 | import com.example.database.models.User; 11 | import com.google.android.gms.tasks.OnCompleteListener; 12 | import com.google.android.gms.tasks.Task; 13 | import com.google.firebase.auth.AuthResult; 14 | import com.google.firebase.auth.FirebaseAuth; 15 | import com.google.firebase.auth.FirebaseUser; 16 | import com.google.firebase.database.DatabaseReference; 17 | import com.google.firebase.database.FirebaseDatabase; 18 | 19 | public class SignInActivity extends BaseActivity implements View.OnClickListener { 20 | private EditText mEmailField, mPasswordField; 21 | private FirebaseAuth mAuth; 22 | 23 | @Override 24 | protected void onCreate(Bundle savedInstanceState) { 25 | super.onCreate(savedInstanceState); 26 | setContentView(R.layout.activity_sign_in); 27 | 28 | mEmailField = findViewById(R.id.field_email); 29 | mPasswordField = findViewById(R.id.field_password); 30 | findViewById(R.id.button_sign_in).setOnClickListener(this); 31 | findViewById(R.id.button_sign_up).setOnClickListener(this); 32 | 33 | 34 | mAuth = FirebaseAuth.getInstance(); 35 | } 36 | 37 | @Override 38 | public void onStart() { 39 | super.onStart(); 40 | if (mAuth.getCurrentUser() != null) { 41 | onAuthSuccess(mAuth.getCurrentUser()); 42 | } 43 | } 44 | 45 | private void onAuthSuccess(FirebaseUser firebaseUser) { 46 | String email = firebaseUser.getEmail(); 47 | String username = email; 48 | if (email != null && email.contains("@")) { 49 | username = email.split("@")[0]; 50 | } 51 | 52 | User user = new User(username, email); 53 | DatabaseReference mDatabase = FirebaseDatabase.getInstance().getReference(); 54 | mDatabase.child("users").child(firebaseUser.getUid()).setValue(user); 55 | 56 | startActivity(new Intent(this, MainActivity.class)); 57 | finish(); 58 | } 59 | 60 | private void signIn() { 61 | String email = mEmailField.getText().toString().trim(); 62 | String password = mPasswordField.getText().toString().trim(); 63 | 64 | if (validateForm(email, password)) { 65 | showProgressDialog(); 66 | mAuth.signInWithEmailAndPassword(email, password).addOnCompleteListener(new OnCompleteListener() { 67 | @Override 68 | public void onComplete(@NonNull Task task) { 69 | hideProgressDialog(); 70 | if (task.isSuccessful()) { 71 | onAuthSuccess(task.getResult().getUser()); 72 | } else { 73 | Toast.makeText(SignInActivity.this, task.getException().getMessage(), Toast.LENGTH_LONG).show(); 74 | } 75 | } 76 | }); 77 | } 78 | } 79 | 80 | private void signUp() { 81 | String email = mEmailField.getText().toString().trim(); 82 | String password = mPasswordField.getText().toString().trim(); 83 | 84 | if (validateForm(email, password)) { 85 | showProgressDialog(); 86 | mAuth.createUserWithEmailAndPassword(email, password).addOnCompleteListener(new OnCompleteListener() { 87 | @Override 88 | public void onComplete(@NonNull Task task) { 89 | hideProgressDialog(); 90 | if (task.isSuccessful()) { 91 | onAuthSuccess(task.getResult().getUser()); 92 | } else { 93 | Toast.makeText(SignInActivity.this, task.getException().getMessage(), Toast.LENGTH_SHORT).show(); 94 | } 95 | } 96 | }); 97 | } 98 | } 99 | 100 | private boolean validateForm(String email, String password) { 101 | if (TextUtils.isEmpty(email)) { 102 | mEmailField.setError(getString(R.string.required)); 103 | return false; 104 | } else if (TextUtils.isEmpty(password)) { 105 | mPasswordField.setError(getString(R.string.required)); 106 | return false; 107 | } else { 108 | mEmailField.setError(null); 109 | mPasswordField.setError(null); 110 | return true; 111 | } 112 | } 113 | 114 | @Override 115 | public void onClick(View v) { 116 | switch (v.getId()) { 117 | case R.id.button_sign_in: 118 | signIn(); 119 | break; 120 | case R.id.button_sign_up: 121 | signUp(); 122 | break; 123 | } 124 | } 125 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/database/SwitchActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.database; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import androidx.appcompat.app.AppCompatActivity; 6 | import android.view.View; 7 | 8 | public class SwitchActivity extends AppCompatActivity { 9 | 10 | @Override 11 | protected void onCreate(Bundle savedInstanceState) { 12 | super.onCreate(savedInstanceState); 13 | setContentView(R.layout.activity_switch); 14 | } 15 | 16 | public void gotoBasic(View view) { 17 | startActivity(new Intent(this, BasicActivity.class)); 18 | finish(); 19 | } 20 | 21 | public void gotoAdvance(View view) { 22 | startActivity(new Intent(this, SignInActivity.class)); 23 | finish(); 24 | } 25 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/database/fragment/MyPostsFragment.java: -------------------------------------------------------------------------------- 1 | package com.example.database.fragment; 2 | 3 | import com.google.firebase.database.DatabaseReference; 4 | import com.google.firebase.database.Query; 5 | 6 | public class MyPostsFragment extends PostListFragment { 7 | public MyPostsFragment() {} 8 | 9 | @Override 10 | public Query getQuery(DatabaseReference databaseReference) { 11 | // All my posts 12 | return databaseReference.child("user-posts").child(getUid()); 13 | } 14 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/database/fragment/MyTopPostsFragment.java: -------------------------------------------------------------------------------- 1 | package com.example.database.fragment; 2 | 3 | import com.google.firebase.database.DatabaseReference; 4 | import com.google.firebase.database.Query; 5 | 6 | public class MyTopPostsFragment extends PostListFragment { 7 | public MyTopPostsFragment() {} 8 | 9 | @Override 10 | public Query getQuery(DatabaseReference databaseReference) { 11 | // My top posts by number of stars 12 | return databaseReference.child("user-posts").child(getUid()).orderByChild("starCount"); 13 | } 14 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/database/fragment/PostListFragment.java: -------------------------------------------------------------------------------- 1 | package com.example.database.fragment; 2 | 3 | import android.app.Activity; 4 | import android.app.Dialog; 5 | import android.content.Intent; 6 | import android.os.Bundle; 7 | import androidx.fragment.app.Fragment; 8 | import androidx.recyclerview.widget.LinearLayoutManager; 9 | import androidx.recyclerview.widget.RecyclerView; 10 | import android.util.Log; 11 | import android.view.LayoutInflater; 12 | import android.view.View; 13 | import android.view.ViewGroup; 14 | import android.widget.LinearLayout; 15 | import android.widget.ProgressBar; 16 | 17 | import com.example.database.PostDetailActivity; 18 | import com.example.database.R; 19 | import com.example.database.models.Post; 20 | import com.example.database.viewholder.PostViewHolder; 21 | import com.firebase.ui.database.FirebaseRecyclerAdapter; 22 | import com.firebase.ui.database.FirebaseRecyclerOptions; 23 | import com.google.firebase.auth.FirebaseAuth; 24 | import com.google.firebase.database.DataSnapshot; 25 | import com.google.firebase.database.DatabaseError; 26 | import com.google.firebase.database.DatabaseReference; 27 | import com.google.firebase.database.FirebaseDatabase; 28 | import com.google.firebase.database.MutableData; 29 | import com.google.firebase.database.Query; 30 | import com.google.firebase.database.Transaction; 31 | 32 | public abstract class PostListFragment extends Fragment { 33 | private Activity mActivity; 34 | private DatabaseReference mDatabase; 35 | private FirebaseRecyclerAdapter mAdapter; 36 | private RecyclerView mRecycler; 37 | 38 | public PostListFragment() { 39 | } 40 | 41 | @Override 42 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 43 | super.onCreateView(inflater, container, savedInstanceState); 44 | View rootView = inflater.inflate(R.layout.fragment_all_posts, container, false); 45 | mRecycler = rootView.findViewById(R.id.messages_list); 46 | mRecycler.setHasFixedSize(true); 47 | 48 | mDatabase = FirebaseDatabase.getInstance().getReference(); 49 | return rootView; 50 | } 51 | 52 | @Override 53 | public void onActivityCreated(Bundle savedInstanceState) { 54 | super.onActivityCreated(savedInstanceState); 55 | mActivity = getActivity(); 56 | 57 | final Dialog mDialog = new Dialog(mActivity, R.style.NewDialog); 58 | mDialog.addContentView( 59 | new ProgressBar(mActivity), 60 | new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT) 61 | ); 62 | mDialog.setCancelable(true); 63 | mDialog.show(); 64 | 65 | // Set up Layout Manager, reverse layout 66 | LinearLayoutManager mManager = new LinearLayoutManager(mActivity); 67 | mManager.setReverseLayout(true); 68 | mManager.setStackFromEnd(true); 69 | mRecycler.setLayoutManager(mManager); 70 | 71 | // Set up FirebaseRecyclerAdapter with the Query 72 | Query postsQuery = getQuery(mDatabase); 73 | 74 | FirebaseRecyclerOptions options = new FirebaseRecyclerOptions.Builder() 75 | .setQuery(postsQuery, Post.class) 76 | .build(); 77 | 78 | mAdapter = new FirebaseRecyclerAdapter(options) { 79 | @Override 80 | protected void onBindViewHolder(PostViewHolder viewHolder, int position, final Post model) { 81 | final DatabaseReference postRef = getRef(position); 82 | 83 | // Determine if the current user has liked this post and set UI accordingly 84 | if (model.stars.containsKey(getUid())) { 85 | viewHolder.starView.setImageResource(R.drawable.ic_toggle_star_24); 86 | } else { 87 | viewHolder.starView.setImageResource(R.drawable.ic_toggle_star_outline_24); 88 | } 89 | 90 | // Bind Post to ViewHolder, setting OnClickListener for the star button 91 | viewHolder.bindToPost(model, new View.OnClickListener() { 92 | @Override 93 | public void onClick(View starView) { 94 | // Need to write to both places the post is stored 95 | DatabaseReference globalPostRef = mDatabase.child("posts").child(postRef.getKey()); 96 | DatabaseReference userPostRef = mDatabase.child("user-posts").child(model.uid).child(postRef.getKey()); 97 | 98 | // Run two transactions 99 | onStarClicked(globalPostRef); 100 | onStarClicked(userPostRef); 101 | } 102 | }); 103 | 104 | viewHolder.itemView.setOnClickListener(new View.OnClickListener() { 105 | @Override 106 | public void onClick(View v) { 107 | Intent intent = new Intent(mActivity, PostDetailActivity.class); 108 | intent.putExtra(PostDetailActivity.EXTRA_POST_KEY, postRef.getKey()); 109 | startActivity(intent); 110 | } 111 | }); 112 | } 113 | 114 | @Override 115 | public PostViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) { 116 | LayoutInflater inflater = LayoutInflater.from(viewGroup.getContext()); 117 | return new PostViewHolder(inflater.inflate(R.layout.item_post, viewGroup, false)); 118 | } 119 | 120 | @Override 121 | public void onDataChanged() { 122 | super.onDataChanged(); 123 | mDialog.dismiss(); 124 | } 125 | }; 126 | mRecycler.setAdapter(mAdapter); 127 | } 128 | 129 | @Override 130 | public void onStart() { 131 | super.onStart(); 132 | if (mAdapter != null) { 133 | mAdapter.startListening(); 134 | } 135 | } 136 | 137 | @Override 138 | public void onStop() { 139 | super.onStop(); 140 | if (mAdapter != null) { 141 | mAdapter.stopListening(); 142 | } 143 | } 144 | 145 | private void onStarClicked(DatabaseReference postRef) { 146 | postRef.runTransaction(new Transaction.Handler() { 147 | @Override 148 | public Transaction.Result doTransaction(MutableData mutableData) { 149 | Post p = mutableData.getValue(Post.class); 150 | if (p == null) { 151 | return Transaction.success(mutableData); 152 | } 153 | 154 | if (p.stars.containsKey(getUid())) { 155 | // Unstar the post and remove self from stars 156 | p.starCount = p.starCount - 1; 157 | p.stars.remove(getUid()); 158 | } else { 159 | // Star the post and add self to stars 160 | p.starCount = p.starCount + 1; 161 | p.stars.put(getUid(), true); 162 | } 163 | 164 | // Set value and report transaction success 165 | mutableData.setValue(p); 166 | return Transaction.success(mutableData); 167 | } 168 | 169 | @Override 170 | public void onComplete(DatabaseError databaseError, boolean b, DataSnapshot dataSnapshot) { 171 | Log.d("postTransaction", "onComplete:" + dataSnapshot.getKey()); 172 | } 173 | }); 174 | } 175 | 176 | public String getUid() { 177 | return FirebaseAuth.getInstance().getCurrentUser().getUid(); 178 | } 179 | 180 | public abstract Query getQuery(DatabaseReference databaseReference); 181 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/database/fragment/RecentPostsFragment.java: -------------------------------------------------------------------------------- 1 | package com.example.database.fragment; 2 | 3 | import com.google.firebase.database.DatabaseReference; 4 | import com.google.firebase.database.Query; 5 | 6 | public class RecentPostsFragment extends PostListFragment { 7 | public RecentPostsFragment() { 8 | } 9 | 10 | @Override 11 | public Query getQuery(DatabaseReference databaseReference) { 12 | // Last 100 posts, these are automatically the 100 most recent 13 | // due to sorting by push() keys 14 | return databaseReference.child("posts").limitToFirst(5); 15 | //return databaseReference.child("posts").orderByChild("title").equalTo("test").limitToLast(2); 16 | 17 | 18 | //return databaseReference.child("posts").orderByKey().startAt("-KRN9eHLLMJbYmJNFz9U").limitToFirst(10); 19 | } 20 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/database/models/Comment.java: -------------------------------------------------------------------------------- 1 | package com.example.database.models; 2 | 3 | import com.google.firebase.database.IgnoreExtraProperties; 4 | 5 | @IgnoreExtraProperties 6 | public class Comment { 7 | public String uid; 8 | public String author; 9 | public String text; 10 | 11 | public Comment() { 12 | // Default constructor required for calls to DataSnapshot.getValue(Comment.class) 13 | } 14 | 15 | public Comment(String uid, String author, String text) { 16 | this.uid = uid; 17 | this.author = author; 18 | this.text = text; 19 | } 20 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/database/models/FriendlyMessage.java: -------------------------------------------------------------------------------- 1 | package com.example.database.models; 2 | 3 | public class FriendlyMessage { 4 | private String text; 5 | private String username; 6 | 7 | public FriendlyMessage() { 8 | } 9 | 10 | public FriendlyMessage(String text, String username) { 11 | this.text = text; 12 | this.username = username; 13 | } 14 | 15 | public String getText() { 16 | return text; 17 | } 18 | 19 | public void setText(String text) { 20 | this.text = text; 21 | } 22 | 23 | public String getUsername() { 24 | return username; 25 | } 26 | 27 | public void setUsername(String username) { 28 | this.username = username; 29 | } 30 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/database/models/Post.java: -------------------------------------------------------------------------------- 1 | package com.example.database.models; 2 | 3 | import com.google.firebase.database.Exclude; 4 | import com.google.firebase.database.IgnoreExtraProperties; 5 | 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | @IgnoreExtraProperties 10 | public class Post { 11 | public String uid; 12 | public String author; 13 | public String title; 14 | public String body; 15 | public int starCount = 0; 16 | public Map stars = new HashMap<>(); 17 | 18 | public Post() { 19 | // Default constructor required for calls to DataSnapshot.getValue(Post.class) 20 | } 21 | 22 | public Post(String uid, String author, String title, String body) { 23 | this.uid = uid; 24 | this.author = author; 25 | this.title = title; 26 | this.body = body; 27 | } 28 | 29 | @Exclude 30 | public Map toMap() { 31 | HashMap result = new HashMap<>(); 32 | result.put("uid", uid); 33 | result.put("author", author); 34 | result.put("title", title); 35 | result.put("body", body); 36 | result.put("starCount", starCount); 37 | result.put("stars", stars); 38 | return result; 39 | } 40 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/database/models/User.java: -------------------------------------------------------------------------------- 1 | package com.example.database.models; 2 | 3 | import com.google.firebase.database.IgnoreExtraProperties; 4 | 5 | @IgnoreExtraProperties 6 | public class User { 7 | public String username; 8 | public String email; 9 | 10 | public User() { 11 | // Default constructor required for calls to DataSnapshot.getValue(User.class) 12 | } 13 | 14 | public User(String username, String email) { 15 | this.username = username; 16 | this.email = email; 17 | } 18 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/database/viewholder/PostViewHolder.java: -------------------------------------------------------------------------------- 1 | package com.example.database.viewholder; 2 | 3 | import androidx.recyclerview.widget.RecyclerView; 4 | import android.view.View; 5 | import android.widget.ImageView; 6 | import android.widget.TextView; 7 | 8 | import com.example.database.R; 9 | import com.example.database.models.Post; 10 | 11 | public class PostViewHolder extends RecyclerView.ViewHolder { 12 | public ImageView starView; 13 | private TextView authorView; 14 | private TextView bodyView; 15 | private TextView numStarsView; 16 | private TextView titleView; 17 | 18 | public PostViewHolder(View itemView) { 19 | super(itemView); 20 | titleView = itemView.findViewById(R.id.post_title); 21 | authorView = itemView.findViewById(R.id.post_author); 22 | starView = itemView.findViewById(R.id.star); 23 | numStarsView = itemView.findViewById(R.id.post_num_stars); 24 | bodyView = itemView.findViewById(R.id.post_body); 25 | } 26 | 27 | public void bindToPost(Post post, View.OnClickListener starClickListener) { 28 | titleView.setText(post.title); 29 | authorView.setText(post.author); 30 | numStarsView.setText(String.valueOf(post.starCount)); 31 | bodyView.setText(post.body); 32 | starView.setOnClickListener(starClickListener); 33 | } 34 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_action_account_circle_40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jirawatee/FirebaseRealtimeDatabase-Android/75cbdccc61701a2725e3bccacb842783b1e50d0c/app/src/main/res/drawable-hdpi/ic_action_account_circle_40.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_image_edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jirawatee/FirebaseRealtimeDatabase-Android/75cbdccc61701a2725e3bccacb842783b1e50d0c/app/src/main/res/drawable-hdpi/ic_image_edit.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_navigation_check_24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jirawatee/FirebaseRealtimeDatabase-Android/75cbdccc61701a2725e3bccacb842783b1e50d0c/app/src/main/res/drawable-hdpi/ic_navigation_check_24.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_toggle_star_24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jirawatee/FirebaseRealtimeDatabase-Android/75cbdccc61701a2725e3bccacb842783b1e50d0c/app/src/main/res/drawable-hdpi/ic_toggle_star_24.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_toggle_star_outline_24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jirawatee/FirebaseRealtimeDatabase-Android/75cbdccc61701a2725e3bccacb842783b1e50d0c/app/src/main/res/drawable-hdpi/ic_toggle_star_outline_24.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_action_account_circle_40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jirawatee/FirebaseRealtimeDatabase-Android/75cbdccc61701a2725e3bccacb842783b1e50d0c/app/src/main/res/drawable-xhdpi/ic_action_account_circle_40.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_image_edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jirawatee/FirebaseRealtimeDatabase-Android/75cbdccc61701a2725e3bccacb842783b1e50d0c/app/src/main/res/drawable-xhdpi/ic_image_edit.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_navigation_check_24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jirawatee/FirebaseRealtimeDatabase-Android/75cbdccc61701a2725e3bccacb842783b1e50d0c/app/src/main/res/drawable-xhdpi/ic_navigation_check_24.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_toggle_star_24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jirawatee/FirebaseRealtimeDatabase-Android/75cbdccc61701a2725e3bccacb842783b1e50d0c/app/src/main/res/drawable-xhdpi/ic_toggle_star_24.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_toggle_star_outline_24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jirawatee/FirebaseRealtimeDatabase-Android/75cbdccc61701a2725e3bccacb842783b1e50d0c/app/src/main/res/drawable-xhdpi/ic_toggle_star_outline_24.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_action_account_circle_40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jirawatee/FirebaseRealtimeDatabase-Android/75cbdccc61701a2725e3bccacb842783b1e50d0c/app/src/main/res/drawable-xxhdpi/ic_action_account_circle_40.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_image_edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jirawatee/FirebaseRealtimeDatabase-Android/75cbdccc61701a2725e3bccacb842783b1e50d0c/app/src/main/res/drawable-xxhdpi/ic_image_edit.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_navigation_check_24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jirawatee/FirebaseRealtimeDatabase-Android/75cbdccc61701a2725e3bccacb842783b1e50d0c/app/src/main/res/drawable-xxhdpi/ic_navigation_check_24.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_toggle_star_24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jirawatee/FirebaseRealtimeDatabase-Android/75cbdccc61701a2725e3bccacb842783b1e50d0c/app/src/main/res/drawable-xxhdpi/ic_toggle_star_24.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_toggle_star_outline_24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jirawatee/FirebaseRealtimeDatabase-Android/75cbdccc61701a2725e3bccacb842783b1e50d0c/app/src/main/res/drawable-xxhdpi/ic_toggle_star_outline_24.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_action_account_circle_40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jirawatee/FirebaseRealtimeDatabase-Android/75cbdccc61701a2725e3bccacb842783b1e50d0c/app/src/main/res/drawable-xxxhdpi/ic_action_account_circle_40.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_image_edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jirawatee/FirebaseRealtimeDatabase-Android/75cbdccc61701a2725e3bccacb842783b1e50d0c/app/src/main/res/drawable-xxxhdpi/ic_image_edit.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_navigation_check_24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jirawatee/FirebaseRealtimeDatabase-Android/75cbdccc61701a2725e3bccacb842783b1e50d0c/app/src/main/res/drawable-xxxhdpi/ic_navigation_check_24.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_toggle_star_24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jirawatee/FirebaseRealtimeDatabase-Android/75cbdccc61701a2725e3bccacb842783b1e50d0c/app/src/main/res/drawable-xxxhdpi/ic_toggle_star_24.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_toggle_star_outline_24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jirawatee/FirebaseRealtimeDatabase-Android/75cbdccc61701a2725e3bccacb842783b1e50d0c/app/src/main/res/drawable-xxxhdpi/ic_toggle_star_outline_24.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jirawatee/FirebaseRealtimeDatabase-Android/75cbdccc61701a2725e3bccacb842783b1e50d0c/app/src/main/res/drawable-xxxhdpi/logo.png -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_chat.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 20 | 21 | 27 | 28 | 35 | 36 |