I have used phone authentication in my app. While authenticating with phone number which is in same mobile it fails and show error for null pointer exception of Realtime database, but while authenticating with phone number which is in another phone and app in another phone it works perfectly. Why it fails for phone number in same mobile containing app?
Otpverify.kt
package com.bookqueen.bookqueen.Activity
class OtpVerfiy : AppCompatActivity() {
lateinit var auth: FirebaseAuth
lateinit var storedVerificationId: String
lateinit var resendToken: PhoneAuthProvider.ForceResendingToken
private lateinit var callbacks: PhoneAuthProvider.OnVerificationStateChangedCallbacks
lateinit var verify: Button
lateinit var resendotp: TextView
lateinit var numbertext: TextView
lateinit var progressBar: ProgressBar
lateinit var database: FirebaseDatabase
lateinit var databaseReference: DatabaseReference
lateinit var number: String
lateinit var otp: EditText
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_otp_verfiy)
auth = FirebaseAuth.getInstance()
verify = findViewById(R.id.btnverify)
resendotp = findViewById(R.id.resendotp)
progressBar = findViewById(R.id.progressBar)
numbertext = findViewById(R.id.intnumber)
otp = findViewById(R.id.edtotp)
progressBar.visibility = View.GONE
database = FirebaseDatabase.getInstance()
databaseReference = database.getReference("Users")
val mobileNumber = intent.getStringExtra("mobilenumber")
number = "+91" + mobileNumber.toString().trim()
numbertext.text = number
verify.setOnClickListener {
//val otp = findViewById<EditText>(R.id.edtotp).text.toString().trim()
if (TextUtils.isEmpty(otp.text.toString())) {
otp.error = getString(R.string.enterotp)
} else if (otp.length() > 0 || otp.length() == 4) {
progressBar.visibility = View.VISIBLE
val credential: PhoneAuthCredential = PhoneAuthProvider.getCredential(
storedVerificationId.toString(), otp.text.toString().trim()
)
signInWithPhoneAuthCredential(credential)
} else {
Toast.makeText(this, getString(R.string.invalidotp), Toast.LENGTH_SHORT).show()
progressBar.visibility = View.GONE
}
}
resendotp.setOnClickListener {
resendcodeforverification(number, resendToken)
Toast.makeText(this, getString(R.string.otpsent), Toast.LENGTH_SHORT).show()
}
callbacks = object : PhoneAuthProvider.OnVerificationStateChangedCallbacks() {
override fun onVerificationCompleted(credential: PhoneAuthCredential) {
startActivity(Intent(applicationContext, MainActivity::class.java))
finish()
}
override fun onVerificationFailed(e: FirebaseException) {
Toast.makeText(applicationContext, getString(R.string.verificationfailed),
Toast.LENGTH_LONG).show()
}
override fun onCodeSent(
verificationId: String,
token: PhoneAuthProvider.ForceResendingToken
) {
storedVerificationId = verificationId
resendToken = token
}
}
sendVerificationcode(number)
}
private fun sendVerificationcode(number: String) {
val options = PhoneAuthOptions.newBuilder(auth)
.setPhoneNumber(number)
.setTimeout(60L, TimeUnit.SECONDS)
.setActivity(this)
.setCallbacks(callbacks)
.build()
PhoneAuthProvider.verifyPhoneNumber(options)
}
private fun resendcodeforverification(
phone: String,
token: PhoneAuthProvider.ForceResendingToken?
) {
val option = PhoneAuthOptions.newBuilder()
.setPhoneNumber(phone)
.setTimeout(60L, TimeUnit.SECONDS)
.setActivity(this)
.setCallbacks(callbacks)
.setForceResendingToken(token!!)
.build()
PhoneAuthProvider.verifyPhoneNumber(option)
}
private fun signInWithPhoneAuthCredential(credential: PhoneAuthCredential) {
auth.signInWithCredential(credential)
.addOnCompleteListener(this) { task ->
if (task.isSuccessful) {
Toast.makeText(this, getString(R.string.signinsuccess),
Toast.LENGTH_SHORT).show()
databaseReference.orderByChild("Phone").equalTo(number)
.addListenerForSingleValueEvent(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
if (snapshot.value != null) {
startActivity(
Intent(
applicationContext,
MainActivity::class.java
)
)
progressBar.visibility = View.GONE
finish()
} else {
startActivity(
Intent(
applicationContext,
SaveProfile::class.java
)
)
progressBar.visibility = View.GONE
finish()
}
}
override fun onCancelled(error: DatabaseError) {
Toast.makeText(this#OtpVerfiy, error.message, Toast.LENGTH_SHORT)
.show()
}
})
} else {
if (task.exception is FirebaseAuthInvalidCredentialsException) {
Toast.makeText(this, getString(R.string.invalidotp),
Toast.LENGTH_SHORT).show()
progressBar.visibility = View.GONE
}
}
}
.addOnFailureListener { e ->
Toast.makeText(this, e.message.toString(), Toast.LENGTH_SHORT).show()
progressBar.visibility = View.GONE
}
}
}
Error Message
2021-11-19 08:58:35.920 26039-26039/com.bookqueen.bookqueen
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.bookqueen.bookqueen, PID: 26039
java.lang.RuntimeException: Unable to start activity
ComponentInfo{com.bookqueen.bookqueen/com.bookqueen.bookqueen
.Activity.MainActivity}: java.lang.NullPointerException at
android.app.ActivityThread.performLaunchActivity
(ActivityThread.java:3534) at
android.app.ActivityThread.handleLaunchActivity
(ActivityThread.java:3689)at
android.app.servertransaction.LaunchActivityItem.execute
(LaunchActivityItem.java:83) at
android.app.servertransaction.TransactionExecutor.executeCallbacks
(TransactionExecutor.java:140)at
android.app.servertransaction.TransactionExecutor.execute
(TransactionExecutor.java:100)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2239)
at android.os.Handler.dispatchMessage(Handler.java:107)
at android.os.Looper.loop(Looper.java:227)
at android.app.ActivityThread.main(ActivityThread.java:7822)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run
(RuntimeInit.java:492)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1026)
Caused by: java.lang.NullPointerException at
com.bookqueen.bookqueen.Activity.MainActivity
.onCreate(MainActivity.kt:40)
at android.app.Activity.performCreate(Activity.java:7969)
at android.app.Activity.performCreate(Activity.java:7958)
at android.app.Instrumentation.callActivityOnCreate
(Instrumentation.java:1306)
at android.app.ActivityThread.performLaunchActivity
(ActivityThread.java:3505)
build.gradle(:app)
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'com.google.gms.google-services'
android {
compileSdkVersion 30
buildToolsVersion "30.0.3"
defaultConfig {
applicationId "com.bookqueen.bookqueen"
minSdkVersion 18
targetSdkVersion 30
versionCode 1
versionName "1.0"
multiDexEnabled true
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-
optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.6.0'
implementation 'androidx.appcompat:appcompat:1.3.1'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.coordinatorlayout:coordinatorlayout:1.1.0'
implementation 'com.google.firebase:firebase-auth:21.0.1'
implementation 'com.google.firebase:firebase-analytics-ktx:19.0.0'
implementation 'com.google.firebase:firebase-database-ktx:20.0.1'
implementation 'com.google.firebase:firebase-firestore-ktx:23.0.3'
implementation 'com.google.firebase:firebase-storage-ktx:20.0.0'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
//material design
implementation 'com.google.android.material:material:1.5.0-alpha01'
// Import the BoM for the Firebase platform
implementation platform('com.google.firebase:firebase-bom:28.3.0')
// Declare the dependency for the Firebase Authentication library
// When using the BoM, you don't specify versions in Firebase library
dependencies
implementation 'com.google.firebase:firebase-auth-ktx'
implementation 'com.google.android.material:material:1.1.0'
implementation 'com.squareup.picasso:picasso:2.71828'
implementation 'com.google.android.material:material:1.3.0-alpha03'
implementation 'de.hdodenhof:circleimageview:3.1.0'
implementation 'com.github.smarteist:autoimageslider:1.4.0'
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
}
Related
My first time asking questions here I think I'm going insane. Working on a school project, I am creating an app like Goodreads, I have recyclerView which lists all the books from database(reading data works fine) and a different fragment where you can write a review for each book. So I have 3 layouts(main_activity layout,review layout and a layout that designs an item for recyclerView).
The problem is when I click save button which is supposed to save a review ni database it does nothing. I have a collection called "books" which has 8 documents and fields such as title,author,rating,image,review. All fields are string types. My rules allow writing and reading data. I have no idea what to do if anyone has better insight I will be really thankful
Main activity:
class MainActivity : AppCompatActivity(),BookRecyclerAdapter.ContentListener {
private lateinit var recyclerAdapter: BookRecyclerAdapter
private val db = Firebase.firestore
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(layout.activity_main)
val recyclerView = findViewById<RecyclerView>(id.booksList)
db.collection("books")
.get() //da dohvati sve dokumente
.addOnSuccessListener { //unutar ovoga pristup svim podadcima koji su se ucitali
val items: ArrayList<Book> = ArrayList()
for (data in it.documents) { //stvori novu data varijablu za svaki dohvaceni dokument(element?)
val book =
data.toObject(Book::class.java) //sve podatke pretvaramo u model preko toObject u Person
if (book != null) {
book.id = data.id //postavljanje ida
items.add(book) //dodavanje gotovog eprsona u listu
}
}
recyclerAdapter = BookRecyclerAdapter(items, this#MainActivity)
recyclerView.apply {
layoutManager = LinearLayoutManager(this#MainActivity)
adapter = recyclerAdapter
}
}.addOnFailureListener { exception ->
Log.w("MainActivity", "Error getting documents", exception)
}
}
override fun onItemButtonClick(index: Int, item: Book, clickType: ItemClickType) {
if (clickType == ItemClickType.SAVE) {
db.collection("books").document(item.id)
.set(item)
.addOnSuccessListener { Log.d(TAG, "DocumentSnapshot successfully written!") }
.addOnFailureListener { e -> Log.w(TAG, "Error writing document", e) }
}
else if (clickType == ItemClickType.REVIEW) {
val newFragment: Fragment = ReviewFragment()
val transaction: FragmentTransaction? = supportFragmentManager.beginTransaction()
transaction?.replace(R.id.container, newFragment);
transaction?.addToBackStack(null);
transaction?.commit();
}
}
}
BookRecyclerAdapter
package hr.ferit.enasalaj.zavrsni
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.EditText
import android.widget.ImageView
import android.widget.Toast
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
enum class ItemClickType {
SAVE,
REVIEW
}
class BookRecyclerAdapter(
val items:ArrayList<Book>,
val listener: ContentListener,
): RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val mainView = LayoutInflater.from(parent.context).inflate(R.layout.recycler_view_books, parent, false)
val reviewView = LayoutInflater.from(parent.context).inflate(R.layout.review, parent, false)
return BookViewHolder(mainView, reviewView)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is BookViewHolder -> {
holder.bind(position, listener, items[position])
}
}
}
override fun getItemCount(): Int {
return items.size
}
class BookViewHolder(val view: View,val review: View): RecyclerView.ViewHolder(view) {
private val bookImage =
view.findViewById<ImageView>(R.id.image)
private val bookAuthor =
view.findViewById<EditText>(R.id.bookAuthor)
private val bookTitle =
view.findViewById<EditText>(R.id.bookTitle)
private val bookRating =
view.findViewById<EditText>(R.id.bookRating)
private val reviewButton =
view.findViewById<Button>(R.id.writeReviewButton)
public val saveButton =
review.findViewById<Button>(R.id.saveButton)
public val bookReview =
review.findViewById<EditText>(R.id.saveReview)
fun bind(
index: Int,
listener: ContentListener,
item: Book,
) {
Glide.with(view.context).load(item.image).into(bookImage)
bookAuthor.setText(item.author)
bookTitle.setText(item.title)
bookRating.setText(item.rating)
bookReview.setText(item.review)
reviewButton.setOnClickListener {
listener.onItemButtonClick(index,item,ItemClickType.REVIEW)
}
saveButton.setOnClickListener{
listener.onItemButtonClick(index,item,ItemClickType.SAVE)
}
}
}
interface ContentListener {
fun onItemButtonClick(index: Int, item: Book, clickType: ItemClickType)
}
}
And here is my gradle file:
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'com.google.gms.google-services'
}
android {
namespace 'hr.ferit.enasalaj.zavrsni'
compileSdk 32
defaultConfig {
applicationId "hr.ferit.enasalaj.zavrsni"
minSdk 21
targetSdk 32
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
dependencies {
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.5.1'
implementation 'com.google.android.material:material:1.7.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'com.google.firebase:firebase-firestore-ktx:24.4.1'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.4'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0'
implementation 'com.github.bumptech.glide:glide:4.14.2'
implementation "com.google.firebase:firebase-auth:9.6.1"
implementation 'com.google.firebase:firebase-database:9.6.1'
annotationProcessor 'com.github.bumptech.glide:compiler:4.14.2'
apply plugin: 'com.google.gms.google-services'
}
I am facing an issue with the Google SignIn using Firebase.
I followed all the mentioned steps (from here) when it comes to the implementations, but I changed the code, since the one provided wasn't very clear.
My app is connected to Firebase and the email authentication works without any issues. However, when I open the app and click on "sign in" with google, I get to choose an account, and then nothing happens.
I found a similar question here, but since my app is in Android Studio (Kotlin), I don't really understand the code and the explanation from Flutter.
As you can see below, the Google Sign In option is activated:
Here is the code from the Login activity:
('client_id' is the Web Client Id that I took from the SDK configuration from Firebase)
#SuppressLint("CheckResult")
class LoginActivity : AppCompatActivity() {
private lateinit var binding: ActivityLoginBinding
private lateinit var auth: FirebaseAuth
private lateinit var googleSignInClient: GoogleSignInClient
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityLoginBinding.inflate(layoutInflater)
setContentView(binding.root)
// Auth
auth = FirebaseAuth.getInstance()
// Config google sign in
val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestIdToken(getString(R.string.client_id))
.requestEmail()
.build()
googleSignInClient = GoogleSignIn.getClient(this, gso)
// Click
binding.googleBtn.setOnClickListener {
signInGoogle()
}
}
private fun signInGoogle(){
val signInIntent = googleSignInClient.signInIntent
launcher.launch(signInIntent)
}
private val launcher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()){
result ->
if (result.resultCode == Activity.RESULT_OK){
val task = GoogleSignIn.getSignedInAccountFromIntent(result.data)
handleResults(task)
}
}
private fun handleResults(task: Task<GoogleSignInAccount>) {
if (task.isSuccessful){
val account: GoogleSignInAccount? = task.result
if (account != null){
updateUI(account)
}
}else{
Toast.makeText(this, task.exception.toString(), Toast.LENGTH_SHORT).show()
}
}
private fun updateUI(account: GoogleSignInAccount) {
val credential = GoogleAuthProvider.getCredential(account.idToken, null)
auth.signInWithCredential(credential).addOnCompleteListener {
if (it.isSuccessful){
val intent = Intent(this, HomeActivity::class.java)
intent.putExtra("email", account.email)
intent.putExtra("name", account.displayName)
startActivity(intent)
}else{
Toast.makeText(this, it.exception.toString(), Toast.LENGTH_SHORT).show()
}
}
}
Also, from the HomeActivity (after signing in, you should go to this activity):
class HomeActivity : AppCompatActivity() {
private lateinit var binding: ActivityHomeBinding
private lateinit var auth: FirebaseAuth
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityHomeBinding.inflate(layoutInflater)
setContentView(binding.root)
auth = FirebaseAuth.getInstance()
val email = intent.getStringExtra("email")
val displayName = intent.getStringExtra("name")
binding.textView.text = email + "\n" + displayName
plugins & dependencies build.gradle (Module):
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'com.google.gms.google-services'
}
dependencies {
implementation 'androidx.core:core-ktx:1.8.0'
implementation 'androidx.appcompat:appcompat:1.5.0'
implementation 'com.google.android.material:material:1.6.1'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'com.google.firebase:firebase-auth-ktx:21.0.7'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
implementation "io.reactivex.rxjava2:rxjava:2.2.19"
implementation "com.jakewharton.rxbinding2:rxbinding:2.0.0"
implementation platform('com.google.firebase:firebase-bom:30.3.2')
implementation 'com.google.android.gms:play-services-auth:20.2.0'
}
build.gradle (Project):
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath "com.android.tools.build:gradle:7.0.4"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.10"
classpath 'com.google.gms:google-services:4.3.13'
}
}
plugins {
id 'com.android.application' version '7.2.1' apply false
id 'com.android.library' version '7.2.1' apply false
id 'org.jetbrains.kotlin.android' version '1.7.10' apply false
}
task clean(type: Delete) {
delete rootProject.buildDir
}
I don't understand what I did wrong, when I click on debug, I get something like this:
Any idea or thought on this would be very useful and appreciated.
Thank you.
Try to configure your project in Google API Console first. You have to add the package and SHA1 signature of your build.
Check this https://developers.google.com/identity/one-tap/android/get-started#api-console
or https://developers.google.com/identity/sign-in/android/start-integrating#configure_a_project
Here is the instruction on how to get SHA1 https://developers.google.com/android/guides/client-auth
i'll Check id's and Everything Was Right!! but I don't know what is the problem.
errors :
Unresolved reference: synthetic
Unresolved reference: btnLogin
Unresolved reference: btnSignUp
Unresolved reference: etEmailAddress
Unresolved reference: etPassword
My LoginActivity
class LoginActivity : AppCompatActivity() {
lateinit var firebaseAuth: FirebaseAuth
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login)
firebaseAuth = FirebaseAuth.getInstance()
btnLogin.setOnClickListener {
login()
}
btnSignUp.setOnClickListener {
val intent = Intent(this, SignupActivity::class.java)
startActivity(intent)
finish()
}
}
private fun login(){
val email = etEmailAddress.text.toString()
val password = etPassword.text.toString()
if (email.isBlank() || password.isBlank()) {
Toast.makeText(this, "Email/password cannot be empty", Toast.LENGTH_SHORT).show()
return
}
firebaseAuth.signInWithEmailAndPassword(email, password).addOnCompleteListener(this){
if(it.isSuccessful){
Toast.makeText(this, "Success", Toast.LENGTH_SHORT).show()
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
finish()
}
else{
Toast.makeText(this, "Authentication Failed", Toast.LENGTH_SHORT).show()
}
}
}
}
My AndroidManifest
<uses-permission android:name="android.permission.INTERNET" />
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/AppThemeNoActionBar">
<activity android:name=".activities.ProfileActivity"/>
<activity android:name=".activities.ResultActivity" />
<activity
android:name=".activities.QuestionActivity"
android:theme="#style/AppTheme" />
<activity android:name=".activities.LoginActivity" />
<activity android:name=".activities.SignupActivity" />
<activity android:name=".activities.LoginIntro" />
<activity android:name=".activities.MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
my Build.Gradle
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'com.google.gms.google-services'
android {
compileSdkVersion 32
defaultConfig {
applicationId "com.cheezycode.quizzed"
minSdkVersion 21
targetSdkVersion 32
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildFeatures {
viewBinding = true
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
namespace 'com.cheezycode.quizzed'
}
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'com.google.firebase:firebase-auth:19.3.2'
implementation 'com.google.firebase:firebase-firestore:22.0.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
implementation 'com.google.android.material:material:1.1.0'
implementation 'com.google.code.gson:gson:2.8.6'
}
Try this once-
You missed the initialization.
Suppose you don't want to do initialization use View binding
USING VIEW BINDING AS PER your gradle use
class LoginActivity : AppCompatActivity() {
lateinit var firebaseAuth: FirebaseAuth
private var _binding: ActivityLoginBinding? = null
private val binding get() = _binding!!
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
_binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
firebaseAuth = FirebaseAuth.getInstance()
binding.btnLogin.setOnClickListener {
login()
}
binding.btnSignUp.setOnClickListener {
val intent = Intent(this, SignupActivity::class.java)
startActivity(intent)
finish()
}
}
private fun login(){
val email = binding.etEmailAddress.text.toString()
val password = binding.etPassword.text.toString()
if (email.isBlank() || password.isBlank()) {
Toast.makeText(this, "Email/password cannot be empty", Toast.LENGTH_SHORT).show()
return
}
override fun onDestroy() {
super.onDestroy()
_binding = null
}
Option 2-
class LoginActivity : AppCompatActivity() {
lateinit var firebaseAuth: FirebaseAuth
private var btnLogin: Button? = null
private var btnSignUp: Button? = null
private var etEmailAddress: EditText? = null
private var etPassword: EditText? = null
lateinit var firebaseAuth: FirebaseAuth
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login)
firebaseAuth = FirebaseAuth.getInstance()
btnLogin = findViewById(R.id.btnLogin)
btnSignUp = findViewById(R.id.btnSignUp)
etEmailAddress = findViewById(R.id.etEmailAddress)
etPassword = findViewById(R.id.etPassword)
btnLogin?.setOnClickListener {
login()
}
btnSignUp?.setOnClickListener {
val intent = Intent(this, SignupActivity::class.java)
startActivity(intent)
finish()
}
}
private fun login() {
val email = etEmailAddress?.text.toString()
val password = etPassword?.text.toString()
if (email.isBlank() || password.isBlank()) {
Toast.makeText(this, "Email/password cannot be empty", Toast.LENGTH_SHORT).show()
return
}
firebaseAuth.signInWithEmailAndPassword(email, password).addOnCompleteListener(this) {
if (it.isSuccessful) {
Toast.makeText(this, "Success", Toast.LENGTH_SHORT).show()
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
finish()
} else {
Toast.makeText(this, "Authentication Failed", Toast.LENGTH_SHORT).show()
}
}
}
I have a Room database that returns a Flow of objects. When I insert a new item into the database, the Flow's collect function only triggers if the insert was performed from the same Fragment/ViewModel.
I have recorded a quick video showcasing the issue: https://www.youtube.com/watch?v=7HJkJ7M1WLg
Here is my code setup for the relevant files:
AchievementDao.kt:
#Dao
interface AchievementDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(achievement: Achievement)
#Query("SELECT * FROM achievement")
fun getAllAchievements(): Flow<List<Achievement>>
}
AppDB.kt:
#Database(entities = [Achievement::class], version = 1, exportSchema = false)
abstract class AppDB : RoomDatabase() {
abstract fun achievementDao(): AchievementDao
}
AchievementRepository.kt:
class AchievementRepository #Inject constructor(appDB: AppDB) {
private val achievementDao = appDB.achievementDao()
suspend fun insert(achievement: Achievement) {
withContext(Dispatchers.IO) {
achievementDao.insert(achievement)
}
}
fun getAllAchievements() = achievementDao.getAllAchievements()
}
HomeFragment.kt:
#AndroidEntryPoint
class HomeFragment : Fragment() {
private val viewModel: HomeViewModel by viewModels()
private lateinit var homeText: TextView
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_home, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
bindViews()
subscribeObservers()
}
private fun bindViews() {
homeText = requireView().findViewById(R.id.txt_home)
requireView().findViewById<ExtendedFloatingActionButton>(R.id.fab_add_achievement).setOnClickListener {
AddAchievementBottomSheet().show(parentFragmentManager, "AddAchievementDialog")
}
requireView().findViewById<ExtendedFloatingActionButton>(R.id.fab_add_achievement_same_fragment).setOnClickListener {
viewModel.add()
}
}
private fun subscribeObservers() {
viewModel.count.observe(viewLifecycleOwner, { count ->
if(count != null) {
homeText.text = count.toString()
} else {
homeText.text = resources.getString(R.string.app_name)
}
})
}
}
HomeViewModel.kt:
class HomeViewModel #ViewModelInject constructor(private val achievementRepository: AchievementRepository) :
ViewModel() {
private val _count = MutableLiveData<Int>(null)
val count = _count as LiveData<Int>
init {
viewModelScope.launch {
achievementRepository.getAllAchievements()
.collect { values ->
// FIXME this is only called when inserting from the same Fragment
_count.postValue(values.count())
}
}
}
fun add() {
viewModelScope.launch {
achievementRepository.insert(Achievement(0, 0, "Test"))
}
}
}
AddAchievementBottomSheet.kt:
#AndroidEntryPoint
class AddAchievementBottomSheet : BottomSheetDialogFragment() {
private val viewModel: AddAchievementViewModel by viewModels()
private lateinit var addButton: MaterialButton
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.dialog_add_achievement, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
addButton = requireView().findViewById(R.id.btn_add_achievement)
addButton.setOnClickListener {
viewModel.add(::close)
}
}
private fun close() {
dismiss()
}
}
AddAchievementBottomSheetViewModel.kt:
class AddAchievementViewModel #ViewModelInject constructor(private val achievementRepository: AchievementRepository) :
ViewModel() {
fun add(closeCallback: () -> Any) {
viewModelScope.launch {
achievementRepository.insert(Achievement(0, 0, "Test"))
closeCallback()
}
}
}
build.gradle (app):
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-kapt'
id 'dagger.hilt.android.plugin'
}
android {
compileSdkVersion 30
defaultConfig {
applicationId "com.marcdonald.achievementtracker"
minSdkVersion 23
targetSdkVersion 30
versionCode 1
versionName "1.0.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
dependencies {
// Kotlin
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.0'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.0'
implementation 'androidx.core:core-ktx:1.3.2'
// Android
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.2.1'
implementation "androidx.activity:activity-ktx:1.1.0"
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0'
// Navigation
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.1'
implementation 'androidx.navigation:navigation-ui-ktx:2.3.1'
// Testing
testImplementation 'junit:junit:4.13.1'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
// Dagger Hilt
implementation 'com.google.dagger:hilt-android:2.29.1-alpha'
implementation 'androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha02'
kapt 'androidx.hilt:hilt-compiler:1.0.0-alpha02'
kapt 'com.google.dagger:hilt-android-compiler:2.29.1-alpha'
// Timber for logging
implementation 'com.jakewharton.timber:timber:4.7.1'
// Room
implementation 'androidx.room:room-runtime:2.2.5'
implementation 'androidx.room:room-ktx:2.2.5'
kapt 'androidx.room:room-compiler:2.2.5'
androidTestImplementation 'androidx.room:room-testing:2.2.5'
}
build.gradle (project):
buildscript {
ext.kotlin_version = "1.4.10"
repositories {
google()
jcenter()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.2.0-alpha16'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'android.arch.navigation:navigation-safe-args-gradle-plugin:1.0.0'
classpath 'com.google.dagger:hilt-android-gradle-plugin:2.29.1-alpha'
}
}
allprojects {
repositories {
google()
jcenter()
maven { url 'https://jitpack.io' }
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
I'm not sure if my understanding of Kotlin Flow is to blame or whether my setup is incorrect in some way, but I'd appreciate some help with the issue.
Make sure you use the same instance of your RoomDatabase. Add a #Singleton where you provide AppDB might do the trick.
Try calling subscribeObservers() in the onStart() lifecycle.
You need to add this dependency:
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0"
Then don't collect the Flow in your ViewModel. Instead map it to your needs and expose it as LiveData like this:
class HomeViewModel #ViewModelInject constructor(private val achievementRepository: AchievementRepository) :
ViewModel() {
val count: LiveData<Int> = achievementRepository
.getAllAchievements()
.map {it.size}
.asLiveData()
fun add() {
viewModelScope.launch {
achievementRepository.insert(Achievement(0, 0, "Test"))
}
}
}
Flow is a cold stream which means you have to manually call Flow.collect{} to get the data.
To continuously observe changes in the database,
Option 1) convert Flow to Livedata,
val count: LiveData<Int> = achievementRepository.getAllAchivements().map {
it.count()
}.asLiveData()
Checkout the solution code in Google Codelab, "Android Room with a View - Kotlin"
Option 2) convert Flow to StateFlow which is a hot stream that you can observe on with StateFlow.collect {}
I am trying to make a bottom sheet; the code seems correct but I am having issues with the build.gradle. I am having trouble when I try to add in implementation "com.google.android.material:material:$material_version". I receive the error
"Could not get unknown property 'material_version' for object of type org.gradle.api.internal.artifacts.dsl.dependencies.DefaultDependencyHandler."
Any assistance would be appreciated
build.gradle:
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion 29
buildToolsVersion "29.0.2"
defaultConfig {
applicationId "com.example.bottom_sheet"
minSdkVersion 21
targetSdkVersion 29
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.core:core-ktx:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation "com.google.android.material:material:$material_version"
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}
BottomSheetActivity.kt
class BottomSheetActivity : AppCompatActivity() {
private lateinit var standardBottomSheetBehavior: BottomSheetBehavior<View>
private val startColor = Color.parseColor("#00FFFFFF")
private val endColor = Color.parseColor("#FFFFFFFF")
private val textColor = Color.parseColor("#FF000000")
private var modalDismissWithAnimation = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_bottom_sheet)
setupButtons()
setupStandardBottomSheet()
//animateStandardBottomSheetStates()
}
private fun setupButtons() {
standardBottomSheetButton.setOnClickListener {
standardBottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
}
}
private fun setupStandardBottomSheet() {
standardBottomSheetBehavior = BottomSheetBehavior.from(standardBottomSheet)
val bottomSheetCallback = object : BottomSheetBehavior.BottomSheetCallback() {
override fun onStateChanged(bottomSheet: View, newState: Int) {
textView.text = when (newState) {
BottomSheetBehavior.STATE_EXPANDED -> "STATE_EXPANDED"
BottomSheetBehavior.STATE_COLLAPSED -> "STATE_COLLAPSED"
BottomSheetBehavior.STATE_DRAGGING -> "STATE_DRAGGING"
BottomSheetBehavior.STATE_HALF_EXPANDED -> "STATE_HALF_EXPANDED"
BottomSheetBehavior.STATE_HIDDEN -> "STATE_HIDDEN"
BottomSheetBehavior.STATE_SETTLING -> "STATE_SETTLING"
else -> null
}
}
override fun onSlide(bottomSheet: View, slideOffset: Float) {
val fraction = (slideOffset + 1f) / 2f
val color = ArgbEvaluatorCompat.getInstance().evaluate(fraction, startColor, endColor)
slideView.setBackgroundColor(color)
}
}
standardBottomSheetBehavior.addBottomSheetCallback(bottomSheetCallback)
standardBottomSheetBehavior.saveFlags = BottomSheetBehavior.SAVE_ALL
textView.setTextColor(textColor)
}
private fun showModalBottomSheet() {
val modalBottomSheet = ModalBottomSheet.newInstance(modalDismissWithAnimation)
modalBottomSheet.show(supportFragmentManager, ModalBottomSheet.TAG)
}
private fun animateStandardBottomSheetStates() {
standardBottomSheet.postDelayed({
standardBottomSheetBehavior.state = BottomSheetBehavior.STATE_HIDDEN
}, 1000L)
standardBottomSheet.postDelayed({
standardBottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
}, 2000L)
standardBottomSheet.postDelayed({
standardBottomSheetBehavior.state = BottomSheetBehavior.STATE_HALF_EXPANDED
}, 3000L)
standardBottomSheet.postDelayed({
standardBottomSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED
}, 4000L)
standardBottomSheet.postDelayed({
standardBottomSheetBehavior.state = BottomSheetBehavior.STATE_HIDDEN
}, 5000L)
}
}
ModalBottomSheet.kt
class ModalBottomSheet : BottomSheetDialogFragment() {
private var dismissWithAnimation = false
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? = inflater.inflate(R.layout.modal_bottom_sheet, container, false)
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
dismissWithAnimation = arguments?.getBoolean(ARG_DISMISS_WITH_ANIMATION) ?: false
(requireDialog() as BottomSheetDialog).dismissWithAnimation = dismissWithAnimation
}
companion object {
const val TAG = "ModalBottomSheet"
private const val ARG_DISMISS_WITH_ANIMATION = "dismiss_with_animation"
fun newInstance(dismissWithAnimation: Boolean): ModalBottomSheet {
val modalBottomSheet = ModalBottomSheet()
modalBottomSheet.arguments = bundleOf(ARG_DISMISS_WITH_ANIMATION to dismissWithAnimation)
return modalBottomSheet
}
}
}
You need to change this line:
implementation "com.google.android.material:material:$material_version"
to this:
implementation 'com.google.android.material:material:1.2.0-alpha03'
or add $material_version parameter to your build.gradle file