Search code examples
androidandroid-fragmentsandroid-intentnfcintentfilter

Application receiving NFC in fragment create new instance of hosted activity


hello to people in stackOverflow

well i got an activity which host 3 fragments( with navController) i need to read the NFC Tag ID(EXTRA_ID) in one of those hosted fragment(GameSessionFragment)

my problem is that each time i scan new NFC Tag, the activity which hosting the fragment (Host) "creates" again, so that it's get me out of the fragment which was presented and navigate to the start destination of the "Host" Activity, i'm trying to avoid the Re-creation of the Host activity any ideas how can i reach that?

if i wasn't clear or you think i may have a better explanation of my issue please write above, in addition, sorry for my english..

this is my code:

Manifest:

<activity android:name=".Host" >
            <intent-filter>
                <action android:name="android.nfc.action.TAG_DISCOVERED" />

                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>

Host Activity:

class Host : AppCompatActivity() {

    var fragment: Fragment? = null

    private var mAdapter: NfcAdapter? = null
    private var mPendingIntent: PendingIntent? = null


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_host)

        //for hide the actionBar (contains Fragment name) on top of the screen
        val actionBar = supportActionBar
        actionBar?.hide()

        val navController = Navigation.findNavController(this, R.id.nav_host_fragment)
        val navView = findViewById<BottomNavigationView>(R.id.nav_view)
        navView?.setupWithNavController(navController)


        NavigationUI.setupWithNavController(navView, navController)


        //tries to stop poping the activity each time NFC Tag scanned
        mAdapter = NfcAdapter.getDefaultAdapter(this)

        mPendingIntent = PendingIntent.getActivity(
            this, 0,
            Intent(this, javaClass).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0
        )
       //toGameSession(whatToDO)


    }



    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        for (fragment in supportFragmentManager.fragments) {
            fragment.onActivityResult(requestCode, resultCode, data)
        }
    }

    override fun onNewIntent(intent: Intent) {
        super.onNewIntent(intent)

        if (NfcAdapter.ACTION_NDEF_DISCOVERED == intent.action) {
            intent.getParcelableArrayExtra(NfcAdapter.EXTRA_ID)?.also { rawMessages ->
                val messages: List<NdefMessage> = rawMessages.map { it as NdefMessage }
                // Process the messages array.
                println(messages)

                if (fragment is UserStatusFragment) {
                    val my: UserStatusFragment = fragment as UserStatusFragment
                    // Pass intent or its data to the fragment's method
                    my.processNFC(intent.getStringExtra(messages.toString()))
                }

            }
        }
    }

}

this is the fragment which there i want to receive and use the NFC Extra_ID

GameSessionFragment

class GameSessionFragment : Fragment() {
    // TODO: Rename and change types of parameters
    private var param1: String? = null
    private var param2: String? = null

    lateinit var nfcTagId : String

    private var mNfcAdapter: NfcAdapter? = null
    private var mPendingIntent: PendingIntent? = null
    private var mNdefPushMessage: NdefMessage? = null


    var mAuth: FirebaseAuth? = null
    lateinit var firebaseUser: FirebaseUser
    private lateinit var databaseReference: DatabaseReference

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
       
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

      
        getNfcTagID()

    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_game_session, container, false)
    }


    private fun getNfcTagID(){
        val tagId : ByteArray? = activity?.intent?.getByteArrayExtra(NfcAdapter.EXTRA_ID)
        if (tagId != null){
            nfcTagId = tagId?.toHexString().toString()
            tv_status.text = nfcTagId


        }
    }

    fun ByteArray.toHexString() = joinToString("") { "%02x".format(it) }


    fun questPartFinish(questPart: String, useruid: String) {
//        val rootRef = FirebaseDatabase.getInstance().reference
//        val scoreRef = rootRef.child("Quests").child(useruid).child("$questPart+QuestItemScanned")
//        scoreRef.runTransaction(object : Handler(), Transaction.Handler {
//            override fun doTransaction(mutableData: MutableData): Transaction.Result {
//                val ifFinish =
//                    mutableData.getValue(Boolean::class.java) ?: return Transaction.success(mutableData)
//
//                mutableData.value = true
////                if (operation == "increaseScore") {
////                    mutableData.value = score + 50
////                } else if (operation == "decreaseScore") {
////                    mutableData.value = score - 1
////                }
//                return Transaction.success(mutableData)
//            }
//
//            override fun onComplete(
//                databaseError: DatabaseError?,
//                b: Boolean,
//                dataSnapshot: DataSnapshot?
//            ) {
//            }
//
//            override fun publish(p0: LogRecord?) {
//                TODO("Not yet implemented")
//            }
//
//            override fun flush() {
//                TODO("Not yet implemented")
//            }
//
//            override fun close() {
//                TODO("Not yet implemented")
//            }
//        })

        databaseReference.child("Quests").child(useruid)
            .child("$questPart" + "QuestItemScanned").setValue(true)

    }

}

i also read here Application receiving NFC always pops up new instance in front

but didn't realize what is the actual thing need to do..

i read about using

mNfcAdapter!!.disableForegroundDispatch(getActivity())

but not sure if that thing also affect me..

UPDATE:

thanks to : Andrew

thanks a lot!! "enableReaderMode" seems just like the thing i needed, I successfully read the tag id, but two problems occurs, that happen together, when i scan the NFC tag i can see the tag id in the "println" so all good by now, im successfully reading it, but when i'm trying to pass it into the TextView which in the fragment it only happens when i'm refreshing the fragment, (and there is the second problem)i see that textview for sec but then, all my layout disappear(in all the fragments in my Host Activity),

got that message "Only the original thread that created a view hierarchy can touch its views"

i can see that function inside those fragment work , it only the layout that disappear, hope you understand me and would. be able to help me with my problem, if you think there is better way to describe the issue you're welcome to say

thanks for the all the people which come to help!

the working fun that all you need! '''

var mNfcAdapter: NfcAdapter? = null

    fun ByteArray.toHexString() = joinToString("") { "%02x".format(it) }

    fun scanForNfcTagId(activity : Activity){
                val options = Bundle()
                // READER_PRESENCE_CHECK_DELAY is a work around for a Bug in some NFC implementations.
                options.putInt(NfcAdapter.EXTRA_READER_PRESENCE_CHECK_DELAY, 1);

                mNfcAdapter = NfcAdapter.getDefaultAdapter(activity)

                val flags =
                    NfcAdapter.FLAG_READER_NO_PLATFORM_SOUNDS or
                            NfcAdapter.FLAG_READER_NFC_A or
                            NfcAdapter.FLAG_READER_NFC_B or
                            NfcAdapter.FLAG_READER_NFC_F or
                            NfcAdapter.FLAG_READER_NFC_V or
                            NfcAdapter.FLAG_READER_NFC_BARCODE


                mNfcAdapter!!.enableReaderMode(activity, NfcAdapter.ReaderCallback { tag ->
                    activity?.runOnUiThread {
                        Log.d("WTF", "Tag discovered")
                        nfcTagId = (tag.id).toHexString()
                        Toast.makeText(
                            activity,
                            "tag detected",
                            Toast.LENGTH_SHORT
                        ).show()


                        if (!actualQuest.firstQuestItemScanned) {
                            hatCallBackAction(activity)

                        } else if (!actualQuest.secondQuestItemScanned) {
                            println("scan for pants")
                            pantsCallBackAction(activity)

                        } else if (!actualQuest.thirdQuestItemScanned) {
                            println("scan for shirt")
                            shirtCallBackAction(activity)
                        } else {
                            println("actualQuest" + actualQuest)
                            Toast.makeText(
                                activity,
                                "Quest is Finish! no more to search here",
                                Toast.LENGTH_LONG
                            ).show()
                        }
                    }
                }, flags, null)
            }

'''


Solution

  • Use the newer and better enableReaderMode API for doing NFC https://developer.android.com/reference/android/nfc/NfcAdapter#enableReaderMode(android.app.Activity,%20android.nfc.NfcAdapter.ReaderCallback,%20int,%20android.os.Bundle)

    This creates a new thread in your application to handle when a Tag is discovered, avoids the activity creating/recreating/pausing/resuming that happens when using Intents to get the NFC data.

    Then in the onTagDiscovered callback method use getId https://developer.android.com/reference/android/nfc/Tag#getId() on the Tag object to get the same data as Extra_ID

    An example of using enableReaderMode is at https://stackoverflow.com/a/64441986/2373819

    Update

    As mentioned in the answer a new thread is created to handle the NFC data and only the UI thread can update the UI

    Also in the comments in the example code

    
    // This gets run in a new thread on Tag detection
    // Thus cannot directly interact with the UI Thread
    public void onTagDiscovered(Tag tag) {
    
    

    So you need to use a method to communicate the data between the threads.

    As simple method is to use runOnUIThread https://developer.android.com/reference/android/app/Activity#runOnUiThread(java.lang.Runnable)

    e.g.

    runOnUiThread(new Runnable() {
      @Override
      public void run() {
        // Set the value of the ID to the textview
        textview.setText(iDtext);
      }
    });