when the app is ran the Edit Text's text is not being accessed and passed for some reason instead this is what it shows in the recycler view and my room database:
https://imgur.com/a/7XjDodL
Sorry if this question doesn't make much sense I didn't really know how to phrase it. Any help is greatly appreciated.
Activity Main
package com.example.todoit
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Toast
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.todoit.data.Todo
import com.example.todoit.data.TodoDataBase
import com.example.todoit.databinding.ActivityMainBinding
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
class MainActivity : AppCompatActivity() {
private lateinit var todoAdapter: TodoAdapter
private lateinit var todoDB: TodoDataBase
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
todoDB = TodoDataBase.getInstance(this)
todoAdapter = TodoAdapter(mutableListOf())
val rvTodoItems = binding.rvTodoItems
val btnAddTodo = binding.btnAddTodo
val btnDeleteTodo = binding.btnDeleteTodo
rvTodoItems.layoutManager = LinearLayoutManager(this)
rvTodoItems.adapter = todoAdapter
btnAddTodo.setOnClickListener {
val todoTitle = binding.etTodoTitle.toString()
if (todoTitle.isNotEmpty()) {
val todo = Todo(null, todoTitle, false)
GlobalScope.launch {
todoDB.todoDao().insertAll(todo)
}
todoAdapter.addTodo(todo)
binding.etTodoTitle.text.clear()
Toast.makeText(this, "Successfully written data", Toast.LENGTH_LONG).show()
} else {
Toast.makeText(this, "There was an error while writing data", Toast.LENGTH_LONG)
.show()
}
}
btnDeleteTodo.setOnClickListener {
todoAdapter.deleteDoneTodos()
Toast.makeText(this, "Selected Todo(s) Deleted", Toast.LENGTH_LONG).show()
}
}
}
TodoAdapter
package com.example.todoit
import android.graphics.Paint.STRIKE_THRU_TEXT_FLAG
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.example.todoit.data.Todo
import kotlinx.android.synthetic.main.item_todo.view.*
class TodoAdapter(
private val todos: MutableList<Todo>,
) : RecyclerView.Adapter<TodoAdapter.TodoViewHolder>() {
class TodoViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TodoViewHolder {
return TodoViewHolder(
LayoutInflater.from(parent.context).inflate(
R.layout.item_todo,
parent,
false
)
)
}
fun addTodo(todo: Todo) {
todos.add(todo)
notifyItemInserted(todos.size - 1)
}
fun deleteDoneTodos() {
todos.removeAll { todo ->
todo.isChecked
}
notifyDataSetChanged()
}
private fun toggleStrikeThrough(tvTodoTitle: TextView, isChecked: Boolean) {
if(isChecked) {
tvTodoTitle.paintFlags = tvTodoTitle.paintFlags or STRIKE_THRU_TEXT_FLAG
} else {
tvTodoTitle.paintFlags = tvTodoTitle.paintFlags and STRIKE_THRU_TEXT_FLAG.inv()
}
}
override fun onBindViewHolder(holder: TodoViewHolder, position: Int) {
val curTodo = todos[position]
holder.itemView.apply {
tvTodoTitle.text = curTodo.title
cbDone.isChecked = curTodo.isChecked
toggleStrikeThrough(tvTodoTitle, curTodo.isChecked)
cbDone.setOnCheckedChangeListener { _, isChecked ->
toggleStrikeThrough(tvTodoTitle, isChecked)
curTodo.isChecked = !curTodo.isChecked
}
}
}
override fun getItemCount(): Int {
return todos.size
}
}
Gradle(app)
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id "kotlin-android-extensions"
}
apply plugin: 'kotlin-kapt'
android {
compileSdk 32
defaultConfig {
applicationId "com.example.todoit"
minSdk 21
targetSdk 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'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
dependencies {
//ROOM
def roomVersion = "2.4.2"
implementation "androidx.room:room-ktx:$roomVersion"
kapt "androidx.room:room-compiler:$roomVersion"
// Navigation Component
implementation 'androidx.navigation:navigation-fragment-ktx:2.2.2'
implementation 'androidx.navigation:navigation-ui-ktx:2.2.2'
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'com.google.android.material:material:1.6.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
// Lifecycle components
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
implementation "androidx.lifecycle:lifecycle-common-java8:2.2.0"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"
// Kotlin components
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.72"
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.5"
api "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.5"
}
If you need anymore code then please let me know.
You might want to change the following assignment:
val todoTitle = binding.etTodoTitle.toString()
to this:
val todoTitle = binding.etTodoTitle.text.toString()
Otherwise any new Todo objects created by the onClick will store the View ID of the editText instead of the value you typed inside it...
Related
I'm having troubles with a class inside "utils" package on my Kotlin code
Classic issues I guess, but I have been looking for solutions on forums and nothing works, I'm just trying the bindPreview method with CameraX
This is my main activity
import android.annotation.SuppressLint
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.util.Size
import androidx.camera.core.CameraSelector
import androidx.camera.core.ImageAnalysis
import androidx.camera.core.Preview
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.core.content.ContextCompat
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.LifecycleOwner
import com.fablab.tensorflowcamerax.databinding.ActivityMainBinding
import com.fablab.tensorflowcamerax.utils.Draw
import com.google.common.util.concurrent.ListenableFuture
import com.google.mlkit.common.model.LocalModel
import com.google.mlkit.vision.common.InputImage
import com.google.mlkit.vision.objects.ObjectDetection
import com.google.mlkit.vision.objects.ObjectDetector
import com.google.mlkit.vision.objects.custom.CustomObjectDetectorOptions
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var objectDetector: ObjectDetector
private lateinit var cameraProviderFuture: ListenableFuture<ProcessCameraProvider>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
cameraProviderFuture = ProcessCameraProvider.getInstance(this)
cameraProviderFuture.addListener({
val cameraProvider = cameraProviderFuture.get()
bindPreview(cameraProvider = cameraProvider)
}, ContextCompat.getMainExecutor(this))
val localModel = LocalModel.Builder()
.setAssetFilePath("object_detection.tflite")
// \\assets\object_detection.tflite
.build()
val customObjectDetectorOptions = CustomObjectDetectorOptions.Builder(localModel)
.setDetectorMode(CustomObjectDetectorOptions.STREAM_MODE)
.enableClassification()
.setClassificationConfidenceThreshold(0.5f)
.setMaxPerObjectLabelCount(3)
.build()
objectDetector = ObjectDetection.getClient(customObjectDetectorOptions)
}
#SuppressLint("UnsafeOptInUsageError")
private fun bindPreview (cameraProvider: ProcessCameraProvider) {
val preview = Preview.Builder().build()
val cameraSelector = CameraSelector.Builder()
.requireLensFacing(CameraSelector.LENS_FACING_BACK)
.build()
preview.setSurfaceProvider(binding.previewView.surfaceProvider)
val imageAnalysis = ImageAnalysis.Builder()
.setTargetResolution(Size(1200,720))
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.build()
imageAnalysis.setAnalyzer(ContextCompat.getMainExecutor(this)
) { imageProxy ->
val rotationDegreesValue = imageProxy.imageInfo.rotationDegrees
val image = imageProxy.image
if (image != null) {
val processImage = InputImage.fromMediaImage(image, rotationDegreesValue)
objectDetector
.process(processImage)
.addOnSuccessListener { objects ->
for (i in objects) {
if (binding.parentLayout.childCount > 1) binding.parentLayout.removeViewAt(1)
val context = binding.root.context
val element = Draw(
context = context,
rect = i.boundingBox,
text = i.labels.firstOrNull()?.text ?: "Undefined"
)
binding.parentLayout.addView(element)
}
imageProxy.close()
}.addOnFailureListener {
Log.v("MainActivity", "Error - ${it.message}")
imageProxy.close()
}
}
}
cameraProvider.bindToLifecycle(this as LifecycleOwner, cameraSelector, preview, imageAnalysis)
}
}
My custom view "Draw"
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Rect
import android.view.View
class Draw(context: Context?, var rect: Rect, var text: String): View(context) {
lateinit var boundaryPaint: Paint
lateinit var textPaint: Paint
init {
init()
}
private fun init(){
boundaryPaint = Paint()
boundaryPaint.color = Color.BLACK
boundaryPaint.strokeWidth = 10f
boundaryPaint.style = Paint.Style.STROKE
textPaint = Paint()
textPaint.color = Color.BLACK
textPaint.textSize = 50f
textPaint.style = Paint.Style.FILL
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
canvas?.drawText(
text,
rect.centerX().toFloat(),
rect.centerY().toFloat(),
textPaint)
canvas?.drawRect(
rect.left.toFloat(),
rect.top.toFloat(),
rect.right.toFloat(),
rect.bottom.toFloat(),
boundaryPaint
)
}
}
And the gradle (I know there are deprecated dependencies, I deliveratedly choose them because compile and work with the method that I'm replicating)
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'kotlin-kapt'
}
android {
compileSdk 33
//buildToolsVersion "30.0.1"
defaultConfig {
minSdk 21
//noinspection ExpiredTargetSdkVersion
targetSdk 30
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'
}
aaptOptions {
noCompress "tflite"
}
dataBinding {
enabled = true
}
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.2.1'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
// Object detection
implementation 'com.google.mlkit:object-detection-custom:16.3.0'
// CameraX
implementation "androidx.camera:camera-camera2:1.3.0-alpha02"
implementation "androidx.camera:camera-lifecycle:1.3.0-alpha02"
implementation "androidx.camera:camera-view:1.3.0-alpha02"
}
The "Draw" class is inside utils package and the IDE gives a warning that says:
"Custom view Draw is missing constructor used by tools: (Context) or (Context,AttributeSet) or (Context,AttributeSet,int)"
I tried with and without declaring context value on the objectDetector for, and nothing happens
I also tried converting to secondary constuctor, but did not working either
The project compiles, when I build an APK and execute the app, I got access to native camera (giving manually hardware permission, but I will work on that detail later) but nothing about object detection and canvas drawing.
Please guide me to find a solution without changing again the dependencies version, and I need to know if the assetFilePath is correct too.
Thank you.
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'
}
When the app is ran, the following errors occur:
Errors:
C:\Users\John\AndroidStudioProjects\Todoit 2\app\build\tmp\kapt3\stubs\debug\com\example\todoit\data\TodoDao.java:11: error: Not sure how to handle insert method's return type.
public abstract java.lang.Object addTodo(#org.jetbrains.annotations.NotNull()
C:\Users\John\AndroidStudioProjects\Todoit 2\app\build\tmp\kapt3\stubs\debug\com\example\todoit\data\TodoDao.java:13: error: Type of the parameter must be a class annotated with #Entity or a collection/array of it.
kotlin.coroutines.Continuation<? super kotlin.Unit> continuation);
Any help would be greatly appreciated.
Code:
Todo
package com.example.todoit.data
import androidx.room.Entity
import androidx.room.PrimaryKey
#Entity(tableName = "todo_data")
data class Todo (
#PrimaryKey val id: Int,
val title: String,
var isChecked: Boolean = false
)
TodoDao
package com.example.todoit.data
import androidx.lifecycle.LiveData
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
#Dao
interface TodoDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun addTodo(todo: Todo)
#Query("SELECT * FROM todo_data ORDER BY id ASC")
fun readAllData(): LiveData<List<Todo>>
}
TodoDataBase
package com.example.todoit.data
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
#Database(entities = [Todo::class],version = 1, exportSchema = false)
abstract class TodoDataBase: RoomDatabase() {
abstract fun todoDao(): TodoDao
companion object{
#Volatile
private var INSTANCE: TodoDataBase? = null
fun getDataBase(context: Context):TodoDataBase{
val tempInstance = INSTANCE
if(tempInstance != null){
return tempInstance
}
synchronized(this){
val instance = Room.databaseBuilder(
context.applicationContext,
TodoDataBase::class.java,
"todo_database"
).build()
INSTANCE = instance
return instance
}
}
}
}
TodoRepository
package com.example.todoit.data
import androidx.lifecycle.LiveData
class TodoRepository(private val todoDao:TodoDao) {
val readAllData: LiveData<List<Todo>> = todoDao.readAllData()
suspend fun addTodo(todo:Todo) {
todoDao.addTodo(todo)
}
}
TodoViewModel
package com.example.todoit.data
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class TodoViewModel(application: Application) : AndroidViewModel(application) {
private val readAllData: LiveData<List<Todo>>
private val repository: TodoRepository
init {
val todoDao = TodoDataBase.getDataBase(application).todoDao()
repository = TodoRepository(todoDao)
readAllData = repository.readAllData
}
fun addTodoToDataBase(todo: Todo) {
viewModelScope.launch(Dispatchers.IO) {
repository.addTodo(todo)
}
}
}
MainActivity
package com.example.todoit
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.todoit.data.Todo
import com.example.todoit.data.TodoViewModel
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
private lateinit var todoAdapter: TodoAdapter
private lateinit var todoViewModel: TodoViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
todoViewModel = ViewModelProvider(this).get(TodoViewModel::class.java)
todoAdapter = TodoAdapter(mutableListOf())
rvTodoItems.layoutManager = LinearLayoutManager(this)
rvTodoItems.adapter = todoAdapter
btnAddTodo.setOnClickListener {
val todoTitle = etTodoTitle.text.toString()
if (todoTitle.isNotEmpty()) {
val todo = Todo(0,todoTitle,false)
etTodoTitle.text.clear()
insertDataToDataBase(todo)
todoAdapter.addTodo(todo)
}
btnDeleteTodo.setOnClickListener {
todoAdapter.deleteDoneTodos()
}
}}
private fun insertDataToDataBase(todo: Todo) {
val todoTitle = etTodoTitle.text.toString()
if(todoTitle.isNotEmpty()) {
//Add data to database
todoViewModel.addTodoToDataBase(todo)
}
}
}
TodoAdapter
package com.example.todoit
import android.graphics.Paint.STRIKE_THRU_TEXT_FLAG
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.example.todoit.data.Todo
import kotlinx.android.synthetic.main.item_todo.view.*
class TodoAdapter(
private val todos: MutableList<Todo>,
) : RecyclerView.Adapter<TodoAdapter.TodoViewHolder>() {
class TodoViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TodoViewHolder {
return TodoViewHolder(
LayoutInflater.from(parent.context).inflate(
R.layout.item_todo,
parent,
false
)
)
}
fun addTodo(todo: Todo) {
todos.add(todo)
notifyItemInserted(todos.size - 1)
}
fun deleteDoneTodos() {
todos.removeAll { todo ->
todo.isChecked
}
notifyDataSetChanged()
}
private fun toggleStrikeThrough(tvTodoTitle: TextView, isChecked: Boolean) {
if (isChecked) {
tvTodoTitle.paintFlags = tvTodoTitle.paintFlags or STRIKE_THRU_TEXT_FLAG
} else {
tvTodoTitle.paintFlags = tvTodoTitle.paintFlags and STRIKE_THRU_TEXT_FLAG.inv()
}
}
override fun onBindViewHolder(holder: TodoViewHolder, position: Int) {
val curTodo = todos[position]
holder.itemView.apply {
tvTodoTitle.text = curTodo.title
cbDone.isChecked = curTodo.isChecked
toggleStrikeThrough(tvTodoTitle, curTodo.isChecked)
cbDone.setOnCheckedChangeListener { _, isChecked ->
toggleStrikeThrough(tvTodoTitle, isChecked)
curTodo.isChecked = !curTodo.isChecked
}
}
}
override fun getItemCount(): Int {
return todos.size
}
}
Gradle(Module)
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id "kotlin-android-extensions"
}
apply plugin: 'kotlin-kapt'
android {
compileSdk 32
defaultConfig {
applicationId "com.example.todoit"
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 {
//ROOM
def roomVersion = "2.4.2"
implementation 'androidx.room:room-ktx:2.2.1'
kapt "androidx.room:room-compiler:2.2.1"
// Navigation Component
implementation 'androidx.navigation:navigation-fragment-ktx:2.2.2'
implementation 'androidx.navigation:navigation-ui-ktx:2.2.2'
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'com.google.android.material:material:1.6.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
// Lifecycle components
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
implementation "androidx.lifecycle:lifecycle-common-java8:2.2.0"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"
// Kotlin components
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.72"
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.5"
api "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.5"
}
Gradle(Project)
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
id 'com.android.application' version '7.2.0' apply false
id 'com.android.library' version '7.2.0' apply false
id 'org.jetbrains.kotlin.android' version '1.5.30' apply false
}
task clean(type: Delete) {
delete rootProject.buildDir
}
I think you should set the dependencies for room as follows:
def roomVersion = "2.4.2"
implementation "androidx.room:room-ktx:$roomVersion"
kapt "androidx.room:room-compiler:$roomVersion"
I've the below code trying to work with SMS Token, i got the token code, but once i sent it in SMS I got no responce!
MainActivity.kt
package com.sms
import android.app.PendingIntent
import android.content.Intent
import android.os.Bundle
import android.telephony.SmsManager
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import com.google.android.gms.auth.api.phone.SmsRetriever
import com.google.android.gms.auth.api.phone.SmsRetrieverClient
import com.sms.ui.theme.SmsTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// val smsManager: SmsManager = SmsManager.getDefault()
val smsManager: SmsManager = getSystemService(SmsManager::class.java)
val appSmsToken = smsManager.createAppSpecificSmsToken(createSmsTokenPendingIntent())
print(appSmsToken)
setContent {
SmsTheme {
// A surface container using the 'background' color from the theme
Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colors.background) {
Greeting(appSmsToken)
}
}
}
}
private fun createSmsTokenPendingIntent(): PendingIntent? {
return PendingIntent.getActivity(
this, 1234,
Intent(this, SmsTokenResultVerificationActivity::class.java),
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT) // setting the mutability flag
}
}
#Composable
fun Greeting(name: String) {
Text(text = "Token is: $name")
}
#Preview(showBackground = true)
#Composable
fun DefaultPreview() {
SmsTheme {
Greeting("Android")
}
}
And SmsTokenResultVerificationActivity.kt is:
package com.sms
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.sms.ui.theme.SmsTheme
class SmsTokenResultVerificationActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
SmsTheme {
// A surface container using the 'background' color from the theme
Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colors.background) {
Greeting("Welcome")
}
}
}
}
#Composable
fun Greeting(name: String) {
Text(text = "hi: $name")
}
}
As another option, I found using SMS retriever API in Android could solve it, but I still interested in using the Token as i want my app to depend on the user device token rather than the app token.
BaseApplication.kt
package com.shishirthedev.smsretriverapi
import android.app.Application
import android.util.Log
import android.widget.Toast
class BaseApplication : Application() {
private val TAG = BaseApplication::class.java.simpleName
override fun onCreate() {
super.onCreate()
// Generate Hash Key >>>>>
val appSignatureHashHelper = AppSignatureHashHelper(this)
Log.e(TAG, "HashKey: " + appSignatureHashHelper.appSignatures[0])
var msg = "HashKey: " + appSignatureHashHelper.appSignatures[0]
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show()
// Storing data into SharedPreferences
val sharedPreferences = getSharedPreferences("MySharedPref", MODE_PRIVATE)
// Creating an Editor object to edit(write to the file)
val myEdit = sharedPreferences.edit()
// Storing the key and its value as the data fetched from edittext
myEdit.putString("token", appSignatureHashHelper.appSignatures[0])
// myEdit.putInt("age", age.getText().toString().toInt())
// Once the changes have been made,
// we need to commit to apply those changes made,
// otherwise, it will throw an error
myEdit.commit()
}
}
AppSignatureHashHelper.kt
package com.shishirthedev.smsretriverapi
import android.annotation.TargetApi
import android.content.Context
import android.content.ContextWrapper
import android.content.pm.PackageManager
import android.util.Base64
import android.util.Log
import java.nio.charset.StandardCharsets
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
import java.util.*
class AppSignatureHashHelper(context: Context?) :
ContextWrapper(context) {// Get all package details
/**
* Get all the app signatures for the current package
*
* #return
*/
val appSignatures: ArrayList<String>
get() {
val appSignaturesHashs = ArrayList<String>()
try {
// Get all package details
val packageName = packageName
val packageManager = packageManager
val signatures = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) {
packageManager.getPackageInfo(
packageName,
PackageManager.GET_SIGNING_CERTIFICATES
).signingInfo.apkContentsSigners
} else {
TODO("VERSION.SDK_INT < P")
}
for (signature in signatures) {
val hash = hash(packageName, signature.toCharsString())
if (hash != null) {
appSignaturesHashs.add(String.format("%s", hash))
}
}
} catch (e: Exception) {
Log.e(TAG, "Package not found", e)
}
return appSignaturesHashs
}
companion object {
val TAG = AppSignatureHashHelper::class.java.simpleName
private const val HASH_TYPE = "SHA-256"
const val NUM_HASHED_BYTES = 9
const val NUM_BASE64_CHAR = 11
#TargetApi(19)
private fun hash(packageName: String, signature: String): String? {
val appInfo = "$packageName $signature"
try {
val messageDigest = MessageDigest.getInstance(HASH_TYPE)
messageDigest.update(appInfo.toByteArray(StandardCharsets.UTF_8))
var hashSignature = messageDigest.digest()
// truncated into NUM_HASHED_BYTES
hashSignature = Arrays.copyOfRange(hashSignature, 0, NUM_HASHED_BYTES)
// encode into Base64
var base64Hash =
Base64.encodeToString(hashSignature, Base64.NO_PADDING or Base64.NO_WRAP)
base64Hash = base64Hash.substring(0, NUM_BASE64_CHAR)
return base64Hash
} catch (e: NoSuchAlgorithmException) {
Log.e(TAG, "No Such Algorithm Exception", e)
}
return null
}
}
}
SMSReceiver.kt`
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import com.google.android.gms.auth.api.phone.SmsRetriever
import com.google.android.gms.common.api.CommonStatusCodes
import com.google.android.gms.common.api.Status
import java.util.regex.Pattern
class SMSReceiver : BroadcastReceiver() {
private var otpListener: OTPReceiveListener? = null
fun setOTPListener(otpListener: OTPReceiveListener?) {
this.otpListener = otpListener
}
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == SmsRetriever.SMS_RETRIEVED_ACTION) {
val extras = intent.extras
val status = extras!![SmsRetriever.EXTRA_STATUS] as Status?
when (status!!.statusCode) {
CommonStatusCodes.SUCCESS -> {
val sms = extras[SmsRetriever.EXTRA_SMS_MESSAGE] as String?
sms?.let {
// val p = Pattern.compile("[0-9]+") check a pattern with only digit
val p = Pattern.compile("\\d+")
val m = p.matcher(it)
if (m.find()) {
val otp = m.group()
if (otpListener != null) {
otpListener!!.onOTPReceived(otp)
}
}
}
}
}
}
}
interface OTPReceiveListener {
fun onOTPReceived(otp: String?)
}
}
MainActivity.kt
package com.shishirthedev.smsretriverapi
import android.content.IntentFilter
import android.os.Bundle
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.google.android.gms.auth.api.phone.SmsRetriever
import com.shishirthedev.smsretriverapi.SMSReceiver.OTPReceiveListener
class MainActivity : AppCompatActivity() {
private var intentFilter: IntentFilter? = null
private var smsReceiver: SMSReceiver? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
var token = findViewById<TextView>(R.id.textView)
// Retrieving the value using its keys the file name
// must be same in both saving and retrieving the data
val sh = getSharedPreferences("MySharedPref", MODE_PRIVATE)
// The value will be default as empty string because for
// the very first time when the app is opened, there is nothing to show
val t = sh.getString("token", "")
// val a = sh.getInt("age", 0)
// We can then use the data
token.text = t
// age.setText(a.toString())
// Init Sms Retriever >>>>
initSmsListener()
initBroadCast()
}
private fun initBroadCast() {
intentFilter = IntentFilter(SmsRetriever.SMS_RETRIEVED_ACTION)
smsReceiver = SMSReceiver()
smsReceiver?.setOTPListener(object : OTPReceiveListener {
override fun onOTPReceived(otp: String?) {
showToast("OTP Received: $otp")
}
})
}
private fun initSmsListener() {
val client = SmsRetriever.getClient(this)
client.startSmsRetriever()
}
override fun onResume() {
super.onResume()
registerReceiver(smsReceiver, intentFilter)
}
override fun onPause() {
super.onPause()
unregisterReceiver(smsReceiver)
}
override fun onDestroy() {
super.onDestroy()
smsReceiver = null
}
private fun showToast(msg: String?) {
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show()
}
}
manifest
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.shishirthedev.smsretriverapi">
<application
android:name=".BaseApplication"
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/Theme.SmsRetriverAPi">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
build.gradle (Module)
plugins {
id 'com.android.application'
id 'kotlin-android'
}
android {
compileSdkVersion 30
buildToolsVersion "29.0.3"
defaultConfig {
applicationId "com.shishirthedev.smsretriverapi"
minSdkVersion 21
targetSdkVersion 30
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 "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.2.1'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
implementation 'com.google.android.gms:play-services-auth:19.0.0'
implementation 'com.google.android.gms:play-services-auth-api-phone:17.5.0'
}
``build.gradle (Project)`
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = "1.4.10"
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.1.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
Credit goes to
SHISHIR
I got a problem of cannot signup and also login (only able to sign up twice and after that couldn't do that anymore). Below are my codes:
this is login activity:
package com.example.mae_assignment
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Patterns
import android.widget.Button
import android.widget.Toast
import com.google.android.material.textfield.TextInputEditText
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.auth.FirebaseUser
class Login : AppCompatActivity() {
private lateinit var auth : FirebaseAuth
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.login)
auth = FirebaseAuth.getInstance()
val btnSignUp = findViewById<Button>(R.id.btnSignUp)
btnSignUp.setOnClickListener{
startActivity(Intent(this, SignUp::class.java))
finish()
}
val btnLogin = findViewById<Button>(R.id.btnSignIn)
btnLogin.setOnClickListener{
doLogin()
}
}
private fun doLogin() {
val password = findViewById<TextInputEditText>(R.id.txtPwd)
val email = findViewById<TextInputEditText>(R.id.txtUE)
if (email.text.toString().isEmpty()){
email.error = "Please enter email."
email.requestFocus()
return
}
if (!Patterns.EMAIL_ADDRESS.matcher(email.text.toString()).matches()){
email.error = "Please enter email."
email.requestFocus()
return
}
if (password.text.toString().isEmpty()){
password.error = "Please enter password."
password.requestFocus()
return
}
auth.createUserWithEmailAndPassword(email.text.toString().trim(), password.text.toString().trim())
.addOnCompleteListener(this) { task->
if (task.isSuccessful){
val user =auth.currentUser
updateUI(user)
}else{
val myToast = Toast.makeText(baseContext, "Login failed.", Toast.LENGTH_SHORT)
myToast.show()
updateUI(null)
}
}
}
public override fun onStart() {
super.onStart()
val currentUser = auth.currentUser
updateUI(currentUser)
}
private fun updateUI(currentUser:FirebaseUser?){
if(currentUser != null){
startActivity(Intent(this, Home::class.java))
val myToast = Toast.makeText(baseContext, "Login successful.", Toast.LENGTH_SHORT)
myToast.show()
} else {
val myToast = Toast.makeText(baseContext, "Login failed.", Toast.LENGTH_SHORT)
myToast.show()
}
}
}
this is signup activity:
package com.example.mae_assignment
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Patterns
import android.widget.Button
import android.widget.TextView
import android.widget.Toast
import com.google.android.material.textfield.TextInputEditText
import com.google.firebase.auth.FirebaseAuth
class SignUp : AppCompatActivity() {
private lateinit var auth : FirebaseAuth
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.signup)
auth = FirebaseAuth.getInstance()
val btnCreateAcc = findViewById<Button>(R.id.btnCreateAccount)
btnCreateAcc.setOnClickListener{
signup()
}
}
private fun signup(){
val txtpassword = findViewById<TextInputEditText>(R.id.txtpassword)
val txtemail = findViewById<TextInputEditText>(R.id.txtemail)
if (txtemail.text.toString().isEmpty()){
txtemail.error = "Please enter email."
txtemail.requestFocus()
return
}
if (!Patterns.EMAIL_ADDRESS.matcher(txtemail.text.toString()).matches()){
txtemail.error = "Please enter email."
txtemail.requestFocus()
return
}
if (txtpassword.text.toString().isEmpty()){
txtpassword.error = "Please enter password."
txtpassword.requestFocus()
return
}
auth.createUserWithEmailAndPassword(txtemail.text.toString().trim(), txtpassword.text.toString().trim())
.addOnCompleteListener(this) { task->
if (task.isSuccessful){
startActivity(Intent(this, Login::class.java))
finish()
}else{
val myToast = Toast.makeText(baseContext, "Sign up failed.", Toast.LENGTH_SHORT)
myToast.show()
}
}
}
}
package com.example.mae_assignment
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Patterns
import android.widget.Button
import android.widget.TextView
import android.widget.Toast
import com.google.android.material.textfield.TextInputEditText
import com.google.firebase.auth.FirebaseAuth
class SignUp : AppCompatActivity() {
private lateinit var auth : FirebaseAuth
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.signup)
auth = FirebaseAuth.getInstance()
val btnCreateAcc = findViewById<Button>(R.id.btnCreateAccount)
btnCreateAcc.setOnClickListener{
signup()
}
}
private fun signup(){
val txtpassword = findViewById<TextInputEditText>(R.id.txtpassword)
val txtemail = findViewById<TextInputEditText>(R.id.txtemail)
if (txtemail.text.toString().isEmpty()){
txtemail.error = "Please enter email."
txtemail.requestFocus()
return
}
if (!Patterns.EMAIL_ADDRESS.matcher(txtemail.text.toString()).matches()){
txtemail.error = "Please enter email."
txtemail.requestFocus()
return
}
if (txtpassword.text.toString().isEmpty()){
txtpassword.error = "Please enter password."
txtpassword.requestFocus()
return
}
auth.createUserWithEmailAndPassword(txtemail.text.toString().trim(), txtpassword.text.toString().trim())
.addOnCompleteListener(this) { task->
if (task.isSuccessful){
startActivity(Intent(this, Login::class.java))
finish()
}else{
val myToast = Toast.makeText(baseContext, "Sign up failed.", Toast.LENGTH_SHORT)
myToast.show()
}
}
}
}
And this is the gradle build:
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = "1.5.0"
repositories {
google()
mavenCentral()
}
dependencies {
classpath "com.android.tools.build:gradle:4.2.1"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.google.gms:google-services:4.3.8'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
mavenCentral()
jcenter() // Warning: this repository is going to shut down soon
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
this is gradle build (app):
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'com.google.gms.google-services'
}
android {
compileSdkVersion 30
buildToolsVersion "30.0.3"
defaultConfig {
applicationId "com.example.mae_assignment"
minSdkVersion 16
targetSdkVersion 30
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 "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'com.google.firebase:firebase-auth:21.0.1'
implementation 'com.firebaseui:firebase-ui-auth:6.4.0'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
implementation platform('com.google.firebase:firebase-bom:28.0.1')
implementation 'com.google.firebase:firebase-analytics-ktx'
}
Please help to solve this TQVM. I have been doing this for like 2 weeks, sigh.