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)
}
'''
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);
}
});