I'm trying to move from activities to fragments
In old activity app when the user clicks on the item he is going to a new activity with item data
Now i need same thing in fragment
Here's my adapter code
override fun onBindViewHolder(holder: PhotosHolder, position: Int) {
val productPhoto = photosArrayList[position]
val key = productPhoto.key
val name = productPhoto.name
val price = productPhoto.price
val photo = productPhoto.photo
val photo2 = productPhoto.photo2
val photo3 = productPhoto.photo3
val link = "http://suleimanmf.com/Photos/"
holder.key.text = key
holder.name.text = name
holder.price.text = price
holder.photo = photo
holder.photo2 = photo2
holder.photo3 = photo3
holder.container.setOnClickListener {
goToProductInfo(productPhoto)
}
}
private fun goToProductInfo(info: Photo) {
val photosFragment = PhotosFragment()
val bundle = Bundle()
bundle.putString("key", info.key)
bundle.putString("name", info.name)
bundle.putString("price", info.price)
bundle.putString("photo", info.photo)
bundle.putString("photo2", info.photo2)
bundle.putString("photo3", info.photo3)
photosFragment.arguments = bundle
(context.applicationContext as MainActivity).supportFragmentManager.beginTransaction()
.apply {
replace(R.id.nav_host_fragment_content_main, photosFragment).addToBackStack(null)
commit()
}
// Old activity
/**
val intent = Intent(context, ProductInfoActivity::class.java)
intent.putExtra("key", info.key)
intent.putExtra("name", info.name)
intent.putExtra("price", info.price)
intent.putExtra("photo", info.photo)
intent.putExtra("photo2", info.photo2)
intent.putExtra("photo3", info.photo3)
startActivity(context, intent, null)
*/
}
}
When I click on the item I get this error
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.suleimanmf.gazarcustomers, PID: 5389
java.lang.ClassCastException: android.app.Application cannot be cast to androidx.appcompat.app.AppCompatActivity
at com.suleimanmf.gazarcustomers.ui.gallery.adapter.PhotosAdapter.goToProductInfo(PhotosAdapter.kt:97)
at com.suleimanmf.gazarcustomers.ui.gallery.adapter.PhotosAdapter.onBindViewHolder$lambda-0(PhotosAdapter.kt:80)
Sorry for my poor English
Thanks in advance
The main problem is that you are getting the application context and casting it to MainActivity which is not possible because as the name declares it's application context only. Actually when you want to work with fragments, the only component which is rrsponsible to hold the fragments inside, is the activity instance.
So, the only thing you need to do is replacing the below code lines:
(context.applicationContext as MainActivity).supportFragmentManager.beginTransaction()
.apply {
replace(R.id.nav_host_fragment_content_main, photosFragment).addToBackStack(null)
commit()
}
With the below lines of code:
(context as MainActivity).supportFragmentManager.beginTransaction()
.apply {
replace(R.id.nav_host_fragment_content_main, photosFragment).addToBackStack(null)
commit()
}
Then your code will work properly.
Related
I have two activities and I am trying to pass image by using Serializable way. How to do this? Is it possible to pass image by using Serializable way? Any ideas please.
val resultImage = findViewById<ImageView>(R.id.resultImage)
val getContent = registerForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? ->
resultImage.setImageURI(uri)
}
val galleryBtn = findViewById<Button>(R.id.galleryBtn)
val nextBtn = findViewById<Button>(R.id.nextBtn)
galleryBtn.setOnClickListener {
getContent.launch("image/*")
}
nextBtn.setOnClickListener {
val takeImage = resultImage.setImageURI(Uri)
val person = Person ()
Intent(this,SecoendActivity::class.java).also {
it.putExtra("EXTRA_PERSON",person)
startActivity(it)
}
}
In kotlin class file:
data class Person(
val imageUrl: Bitmap
): Serializable
My second activity:
val imageView = findViewById<ImageView>(R.id.imageView)
val person = intent.getSerializableExtra("EXTRA_PERSON")as Person
It is not recommended to use Serializable to pass images between activities, as it can cause memory and performance issues. Instead, you can use the Intent.putExtra method to pass the image's URI to the other activity, and then use the ContentResolver to retrieve the image in the second activity.
// In the first activity
val imageUri = Uri.parse("content://...")
val intent = Intent(this, SecondActivity::class.java)
intent.putExtra("image_uri", imageUri)
startActivity(intent)
// In the second activity
val imageUri = intent.getParcelableExtra<Uri>("image_uri")
val bitmap = MediaStore.Images.Media.getBitmap(contentResolver, imageUri)
inside MainActivity I have snapShot listener to document added that calls a function inside a fragment that supose to set and update the adapter of item that stored in fire store
mFireStore.collection(Constans.BOARDS)
.whereArrayContains(Constans.ASSIGNED_TO,FireStore().getCurrentUid())
.orderBy("timeStamp", Query.Direction.DESCENDING)
.addSnapshotListener { value, e ->
Log.d("MainActivity","board listener")
if (e != null) {
Log.w(ContentValues.TAG, "Listen failed.", e)
return#addSnapshotListener
}
val boards = ArrayList<Board>()
Constans.BOARDS_CHATS_LIST = ArrayList()
for (doc in value!!) {
val board = doc.toObject(Board()::class.java)
Constans.BOARDS_CHATS_LIST.add(board)
}
fragment_chat().updateBoardToUi(Constans.BOARDS_CHATS_LIST)
}
and here is the function
fun updateBoardToUi(boardsChatsList: ArrayList<Board>) {
if(boardsChatsList.size > 0){
val context = getContext() ?: return
Log.e("${Constans.BOARDS_CHATS_LIST.size.toString()}","updateBoardToUi")
view?.rv_chats_list?.visibility = View.VISIBLE
view?.no_chats_avlible?.visibility = View.GONE
view?.rv_chats_list?.layoutManager = LinearLayoutManager(context)
view?.rv_chats_list?.setHasFixedSize(true)
//might be an error
adapter = BoardItemsAdapter(context,Constans.BOARDS_CHATS_LIST)
view?.rv_chats_list?.adapter = adapter
adapter.notifyItemInserted(0)
adapter.setOnClickListener(
object :BoardItemsAdapter.OnClickListener{
override fun onClick(position: Int, model: Board) {
Log.i("fragment chat", "on click")
val intent = Intent(context, ChatActivity::class.java)
intent.putExtra(Constans.BOARD_CHAT_DETAILS, model)
intent.putExtra("uid", FirebaseAuth.getInstance().currentUser?.uid )
intent.putExtra(Constans.DOCUMENT_ID, model.documentId)
intent.putExtra("position", position)
startActivity(intent)
}
}
)
}else{
Log.e("inside","updateBoardToUi2")
view?.no_chats_avlible?.visibility = View.VISIBLE
}
}
but the adapter deas not show the new item added even thogh I use adapter.notifyItemInserted(0)
It is because you can not hold and send data with "Constants.BOARDS_CHATS_LIST". Because every time you want to call it, it will return the default value it has. You can do 4 things that come into my mind:
1- Send the data from activity to fragment via Shared Preferences. I do not recommend this method.
2 - Send data from activity to fragment via bundle. This is doable but i do not prefer it.
3 - Move your firestore function to the fragment and declare a global list and put the records there, then use it in updateBoardToUi function. You can do this but if you need this function in other fragment, you need to copy and paste it there too.
4- You can create a new class for firestore functions, and whenever you need it, call it from there. This is the best way and i will try to help you with it.
Create new kotlin class and paste this inside it. You will later call this inside onViewCreated of your fragment, and it will send the array to the updateBoardToUi method.
class FirestoreClass {
private val mFireStore = FirebaseFirestore.getInstance()
private val mFirebaseAuth = FirebaseAuth.getInstance()
fun getBoards(fragment: YourFragmentName) {
mFireStore.collection(Constans.BOARDS)
.whereArrayContains(Constans.ASSIGNED_TO,getCurrentUserID())
.orderBy("timeStamp", Query.Direction.DESCENDING)
.addSnapshotListener { value, e ->
if (e != null) {
Log.w(ContentValues.TAG, "Listen failed.", e)
return#addSnapshotListener
}
val boards = ArrayList<Board>()
for (doc in value!!) {
val board = doc.toObject(Board()::class.java)
boards.add(board)
}
fragment.updateBoardToUi(boards)
}
}
fun getCurrentUserID(): String {
val currentUser = mFirebaseAuth.currentUser
var currentUserID = ""
if (currentUser != null) {
currentUserID = currentUser.uid
}
return currentUserID
}
}
Now we will use the list from your db.
fun updateBoardToUi(boardsChatsList: ArrayList<Board>) {
// fragment.updateBoardToUi(boards) that sent the data and now
// it is in boardsChatsList, you will use this.
if(boardsChatsList.size > 0){
val context = getContext() ?: return
Log.e("${boardsChatsList.size.toString()}","updateBoardToUi")
view?.rv_chats_list?.visibility = View.VISIBLE
view?.no_chats_avlible?.visibility = View.GONE
adapter = BoardItemsAdapter(context,boardsChatsList)
view?.rv_chats_list?.adapter = adapter
view?.rv_chats_list?.layoutManager = LinearLayoutManager(context)
view?.rv_chats_list?.setHasFixedSize(true)
adapter.setOnClickListener(
object :BoardItemsAdapter.OnClickListener{
override fun onClick(position: Int, model: Board) {
Log.i("fragment chat", "on click")
val intent = Intent(context,ChatActivity::class.java)
intent.putExtra(Constans.BOARD_CHAT_DETAILS, model)
intent.putExtra("uid", FirestoreClass().getCurrentUserID())
intent.putExtra(Constans.DOCUMENT_ID, model.documentId)
intent.putExtra("position", position)
startActivity(intent)
}
}
)
}else{
Log.e("inside","updateBoardToUi2")
view?.no_chats_avlible?.visibility = View.VISIBLE
}
}
And finally call that db function in your fragment's onViewCreated to activate all of this. If you do not have onViewCreated just paste this code:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
FirestoreClass().getUsersList(this)
}
All of this can be too much, but this is the best practice. If you learn this convention, you will easily adapt working anywhere.
I implemented a registerForActivityResult in fragment to launch another activity and to get data from the activity. The problem is that the registerForActivityResult sometimes works and sometimes it doesn't. I put a log to be printed and I found that when it works the log is not printed. That means it doesn't go into the function.
val getContent = registerForActivityResult(ActivityResultContracts.StartActivityForResult()){ result:ActivityResult ->
Log.i("register", "out")
if(result.resultCode == Activity.RESULT_OK)
{
val data = result.data
val note = data?.getParcelableExtra<Note>("note")
Log.i("register", "in")
adapter.mNotes[notePos].title = note?.title.toString()
adapter.mNotes[notePos].notes = note?.notes.toString()
adapter.notifyItemChanged(notePos)
}
}
private fun initRecycleView()
{
adapter = NotesAdapter(notesList){note, pos ->
val intent = Intent(this.context, NotingActivity::class.java).apply {
putExtra("note",note)
notePos = pos
}
getContent.launch(intent)
}
rvItems.adapter = adapter
rvItems.layoutManager = GridLayoutManager(this.context, 2)
(rvItems.itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
}
override fun onBackPressed() {
intent.putExtra("note", note)
setResult(Activity.RESULT_OK, intent)
finish()
super.onBackPressed()
}
Mine Main Activity :-
var sec = second_activity()
var button1: Button? = null
var button2: Button? = null
var button3: Button? = null
var button4: Button? = null
button1 = findViewById<View>(R.id.button1) as Button
button2 = findViewById<View>(R.id.button2) as Button
button3 = findViewById<View>(R.id.button3) as Button
button4 = findViewById<View>(R.id.button4) as Button
button1?.setOnClickListener {
sec.input = "a"
val intent: Intent = Intent(this, second_activity::class.java)
startActivity(intent)
}
button2?.setOnClickListener {
sec.input = "b"
val intent: Intent = Intent(this, second_activity::class.java)
startActivity(intent)
}
Second activity :-
class second_activity : AppCompatActivity(), NavigationView.OnNavigationItemSelectedListener {
private lateinit var toolbar: Toolbar
private lateinit var mDrawerLayout: DrawerLayout
var input : String = " "
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_second)
//getting recyclerview from xml
val recyclerView = findViewById<RecyclerView>(R.id.recyclerView)
//adding a layoutmanager
recyclerView.layoutManager = LinearLayoutManager(this, RecyclerView.VERTICAL, false)
//crating an arraylist to store users using the data class user
val users = ArrayList<User>()
//adding some dummy data to the list
//creating our adapter
val adapter = CustomAdapter(this , users)
//now adding the adapter to recyclerview
recyclerView.adapter = adapter
Toast.makeText(this, input , Toast.LENGTH_SHORT).show()
when(input) {
"a" -> {
users.add(User(R.drawable.bc))
}
"b" -> {
users.add(User(R.drawable.bc))
users.add(User(R.drawable.bc))
users.add(User(R.drawable.bc))
users.add(User(R.drawable.bc))
}
}
}
Input value is not updating which I pass from MainActivity. It is always taking the blank value which are present in second activity. I also tried by changing the position of Input. but not worked. please help
Is there any other way to find which button is clicked in MainActivity from Second Activity
Yes, there is another way. Use intent.putExtra("requestCode", requestCode). Then in the second activity you can get that requestCode with getIntent().getExtra().getInt("requestCode").
Yes, you can pass the values from one activity to another activity using the following code,
val intent: Intent = Intent(this, SecondActivity::class.java)
intent.putExtra(name, value)
startActivity(intent)
I have given sample code below, please try this..
In first activity, I send the "language" value.
val intent: Intent = Intent(this, SecondActivity::class.java)
intent.putExtra("language", "tamil")
startActivity(intent)
In second activity, I retrived the "language" value.
Put this code in your second activity onCreate()
val intent: Intent = getIntent()
var language = ""
if (intent != null) {
language = intent.getStringExtra("language")
}
println("Language : $language")
I'm getting frustrated with this error Fragment not attached to an activity, I have a fragment that starts an activity and activity starts another activity so on and so forth F -> A1 -> A2 -> A3, after I finished with the last one A3 I call to go back to the hosted activity
val intent = Intent(applicationContext, TabbarActivity::class.java)
intent.putExtra("finish", "true")
intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TASK.or(Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(intent)
finish()
but when I set Intent.FLAG_ACTIVITY_CLEAR_TASK it disattached my fragment from the activity and then i get this error Fragment not attached to an activity, every thing will work fine if i rerun my app.
here is the host Activity
private var bottomNavigation: BottomNavigationView? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_tabbar)
val finish = intent.getStringExtra("finish")
bottomNavigation = findViewById(R.id.bottomNavigationView)
val navigationController = findNavController(R.id.fragment)
bottomNavigation?.setupWithNavController(navigationController)
bottomNavigation?.itemIconTintList = null
val firstFragment: Fragment = Fragment1()
val secondFragment: Fragment = Fragment2()
val thirdFragment: Fragment = Fragment3()
val forthFragment: Fragment = Fragment4()
var active = firstFragment
supportFragmentManager.beginTransaction().add(R.id.containerb, forthFragment, "4").hide(forthFragment).commit()
supportFragmentManager.beginTransaction().add(R.id.containerb, thirdFragment, "3").hide(thirdFragment).commit()
supportFragmentManager.beginTransaction().add(R.id.containerb, secondFragment, "2").hide(secondFragment).commit()
supportFragmentManager.beginTransaction().add(R.id.containerb, firstFragment, "1").commit()
if (finish.equals("true")){
bottomNavigation?.setSelectedItemId(R.id.ordersFragment);
supportFragmentManager.beginTransaction().hide(active).show(thirdFragment).commit()
active = thirdFragment
}
bottomNavigation?.setOnNavigationItemReselectedListener {
when (it.itemId) {
R.id.Fragment1 -> {
supportFragmentManager.beginTransaction().hide(active).show(firstFragment).commit()
active = firstFragment
}
R.id.Fragment2 -> {
supportFragmentManager.beginTransaction().hide(active).show(secondFragment).commit()
active = secondFragment
}
R.id.Fragment3 -> {
supportFragmentManager.beginTransaction().hide(active).show(thirdFragment).commit()
active = thirdFragment
}
R.id.Fragment4 -> {
supportFragmentManager.beginTransaction().hide(active).show(forthFragment).commit()
active = forthFragment
}
}
true
}
bottomNavigation?.setOnNavigationItemSelectedListener { item ->
when (item.itemId) {
R.id.Fragment1 -> {
supportFragmentManager.beginTransaction().hide(active).show(firstFragment).commit()
active = firstFragment
}
R.id.Fragment2 -> {
supportFragmentManager.beginTransaction().hide(active).show(secondFragment).commit()
active = secondFragment
}
R.id.Fragment3 -> {
supportFragmentManager.beginTransaction().hide(active).show(thirdFragment).commit()
active = thirdFragment
}
R.id.Fragment4 -> {
supportFragmentManager.beginTransaction().hide(active).show(forthFragment).commit()
active = forthFragment
}
}
true
}
}
when im getting this error?
after finish with the last activity and the user navigate back user can receive a notification the error starts with this method when the notification request a context i have tried requireContext(), this#..Activity, applicationContext, activity.context, and all the possibilities to get the context Here is the Fragment3
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
createNotificationChannele()
}
private fun newOrderOffers() {
val uid = FirebaseAuth.getInstance().uid ?: ""
val firestore = FirebaseFirestore.getInstance()
val fireRef = firestore.collection("inProcess").whereEqualTo("userUid", uid)
fireRef.addSnapshotListener { value, error ->
if (value != null) {
value.documentChanges.forEach {
if (it.type == DocumentChange.Type.ADDED) {
SendNotificationWhenSelected()
}
}
}
}
}
private fun createNotificationChannele(){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
val channel = NotificationChannel(chanal_id, chanale_name,
NotificationManager.IMPORTANCE_HIGH).apply {
lightColor = Color.RED
enableLights(true)
}
val manager = this.context?.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
manager.createNotificationChannel(channel)
}
}
private fun SendNotificationWhenSelected(){
val notification =
NotificationCompat.Builder(requireContext(), chanal_id) // the error in this line
.setContentTitle("...")
.setContentText("....")
.setSmallIcon(R.drawable.....)
.setPriority(Notification.PRIORITY_HIGH)
.setAutoCancel(true)
.build()
val notificationManager = NotificationManagerCompat.from(requireView().context)
notificationManager.notify(notification_id, notification)
}
the logcat error
java.lang.IllegalStateException: Fragment Fragment3{3131d71} (ee9f090f-fa37-46a0-9fda-b7e9831db10d)} not attached to an activity.
at androidx.fragment.app.Fragment.requireActivity(Fragment.java:833)
at com.qp.dele.fragment.Fragment3.SendNotificationWhenSelected(3Fragment.kt:295)
at com.qp.dele.fragment.Fragment3.access$SendNotificationWhenSelected(Fragment3.kt:40)
at com.qp.dele.fragment.Fragment3$newOrderOffers$1.onEvent(Fragment3.kt:272)
at com.qp.dele.fragment.Fragment3$newOrderOffers$1.onEvent(Fragment3.kt:40)
at com.google.firebase.firestore.Query.lambda$addSnapshotListenerInternal$2(Query.java:1133)
at com.google.firebase.firestore.Query$$Lambda$3.onEvent(Unknown Source:6)
at com.google.firebase.firestore.core.AsyncEventListener.lambda$onEvent$0(AsyncEventListener.java:42)
at com.google.firebase.firestore.core.AsyncEventListener$$Lambda$1.run(Unknown Source:6)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:223)
at android.app.ActivityThread.main(ActivityThread.java:7656)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
It looks like the problem is that the old Fragment is still there and is still getting the Firebase callback after it has been detached from the old Activity. You'll need to remove the Firebase callbacks when the old Fragment is detached/destroyed.
This is why you see this problem when you return back to TabbarActivity but not when you start the app for the first time.