I have built a QR scanning application (written in Kotlin, for Android). It scans for a QR code and then returns the URL after its scanned like so.
However, I wish to take it a step further and actually launch the return value of the QR code into a search engine of an internet application and have it display the results of what the QR code had intended. How would I get the application to get the returned URL and redirect the user to the intended place?
Here is my MainActivity.kt for reference:
class MainActivity : AppCompatActivity() {
private lateinit var codeScanner: CodeScanner
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val scannerView = findViewById<CodeScannerView>(R.id.scanner_view)
codeScanner = CodeScanner(this, scannerView)
codeScanner.camera = CodeScanner.CAMERA_BACK
codeScanner.formats = CodeScanner.ALL_FORMATS
codeScanner.autoFocusMode = AutoFocusMode.SAFE
codeScanner.scanMode = ScanMode.SINGLE
codeScanner.isAutoFocusEnabled = true
codeScanner.isFlashEnabled = false
codeScanner.decodeCallback = DecodeCallback {
runOnUiThread {
Toast.makeText(this, "Scan result: ${it.text}", Toast.LENGTH_LONG).show()}
}
scannerView.setOnClickListener {
codeScanner.startPreview()
}
}
override fun onResume() {
super.onResume()
codeScanner.startPreview()
}
override fun onPause() {
codeScanner.releaseResources()
super.onPause()
}
}
I used this answer to build a searchWeb function within my MainActivity.kt class like so;
fun searchWeb(query: String) {
val url = "http://www.google.com"
val intent = Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(url))
startActivity(intent)
}
I then called the function within my codeScanner.decodeCallback like so;
codeScanner.decodeCallback = DecodeCallback {
runOnUiThread {
Toast.makeText(this, "Scan result: ${it.text}", Toast.LENGTH_LONG).show()
}
searchWeb(it.text)
}
After doing this, the implicit intent was launched and the QR application launched the internet browser.
Related
Integrating Jitsi Sample in my project. but meeting unable to establish getting Cannot join, view is null error in logs. can anyone suggest what could be the issue?
class MainActivity : AppCompatActivity() {
lateinit var binding: ActivityMainBinding
private val broadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
onBroadcastReceived(intent)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
val serverURL: URL = try {
URL("https://meet.jit.si")
} catch (e: MalformedURLException) {
e.printStackTrace()
throw RuntimeException("Invalid server URL!")
}
val defaultOptions = JitsiMeetConferenceOptions.Builder()
.setServerURL(serverURL)
// When using JaaS, set the obtained JWT here
//.setToken("MyJWT")
// Different features flags can be set
//.setFeatureFlag("toolbox.enabled", false)
//.setFeatureFlag("filmstrip.enabled", false)
.setFeatureFlag("welcomepage.enabled", false)
.build()
JitsiMeet.setDefaultConferenceOptions(defaultOptions)
registerForBroadcastMessages()
binding.button4.setOnClickListener{
onButtonClick()
}
}
override fun onDestroy() {
LocalBroadcastManager.getInstance(this).unregisterReceiver(broadcastReceiver)
super.onDestroy()
}
private fun onButtonClick() {
val text = binding.conferenceName.text.toString()
if (text.isNotEmpty()) {
// Build options object for joining the conference. The SDK will merge the default
// one we set earlier and this one when joining.
val options = JitsiMeetConferenceOptions.Builder()
.setRoom(text)
// Settings for audio and video
//.setAudioMuted(true)
//.setVideoMuted(true)
.build()
// Launch the new activity with the given options. The launch() method takes care
// of creating the required Intent and passing the options.
JitsiMeetActivity.launch(this, options)
}
}
private fun registerForBroadcastMessages() {
val intentFilter = IntentFilter()
/* This registers for every possible event sent from JitsiMeetSDK
If only some of the events are needed, the for loop can be replaced
with individual statements:
ex: intentFilter.addAction(BroadcastEvent.Type.AUDIO_MUTED_CHANGED.action);
intentFilter.addAction(BroadcastEvent.Type.CONFERENCE_TERMINATED.action);
... other events
*/
for (type in BroadcastEvent.Type.values()) {
intentFilter.addAction(type.action)
}
LocalBroadcastManager.getInstance(this).registerReceiver(broadcastReceiver, intentFilter)
}
// Example for handling different JitsiMeetSDK events
private fun onBroadcastReceived(intent: Intent?) {
if (intent != null) {
val event = BroadcastEvent(intent)
when (event.type) {
BroadcastEvent.Type.CONFERENCE_JOINED -> Timber.i("Conference Joined with url%s", event.getData().get("url"))
BroadcastEvent.Type.PARTICIPANT_JOINED -> Timber.i("Participant joined%s", event.getData().get("name"))
else -> Timber.i("Received event: %s", event.type)
}
}
}
// Example for sending actions to JitsiMeetSDK
private fun hangUp() {
val hangupBroadcastIntent: Intent = BroadcastIntentHelper.buildHangUpIntent()
LocalBroadcastManager.getInstance(this.applicationContext).sendBroadcast(hangupBroadcastIntent)
}
}
added below dependency.
dependencies {
// Jitsi Meet
implementation "org.jitsi.react:jitsi-meet-sdk:5.1.0"
}
Sharing log information also. this sample I am integrating into kotlin multiplatform project.
please suggest to resolve this issue.
How to get result from another activity (registerForActivity) from with in ktor's Routing API call (eg. /POST) running in a non-activity class?
Background: For an Android app, I run ktor server engine 'netty' in a non-activity class HttpServer.kt. I need to call another app's activity from with in ktor's Routing' POST handler, so I pass 'appCompatActivity' from MainActivity.kt. That's done, just because, I assume, registerForActivityResult() has dependency on UI/life cycle class.
Problem arises when running this as below, as registerForActivityResult() requires to be run earlier (like onCreate() ?), and I don't have such a class in this non-activity class. Moreover, the callback to run when ActivityResult is returned needs to call ktor ApplicationCall's respond which is also a suspend function.
class HttpServer(
private val applicationContext: AppCompatActivity
) {
private val logger = LoggerFactory.getLogger(HttpServer::class.java.simpleName)
private val server = createServer()
private fun ApplicationCall.startSaleActivityForResult() { // <====== *
val activityLauncherCustom =
applicationContext.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult ->
if (result.resultCode == Activity.RESULT_OK || result.resultCode == Activity.RESULT_CANCELED) {
val transactionResultReturned = result.data
// Handle the returned result properly using transactionResultReturned
GlobalScope.launch {
respond(status = HttpStatusCode.OK, TransactionResponse())
}
}
}
val intent = Intent()
// Ignoring statements to create proper action/data intent
activityLauncherCustom.launch(intent) // <====== *
}
fun start() = server.start()
fun stop() = server.stop(0, 0)
private fun createServer(): NettyApplicationEngine {
return GlobalScope.embeddedServer(Netty) {
install(CallLogging)
install(ContentNegotiation) {
gson {
setPrettyPrinting()
}
}
routing {
route("/") {
post {
call.startSaleActivityForResult() // <====== *
}
}
}
}
}
private fun <TEngine : ApplicationEngine, TConfiguration : ApplicationEngine.Configuration>
CoroutineScope.embeddedServer(
factory: ApplicationEngineFactory<TEngine, TConfiguration>,
module: Application.() -> Unit
): TEngine {
val environment = applicationEngineEnvironment {
this.parentCoroutineContext = coroutineContext + parentCoroutineContext
this.log = logger
this.module(module)
connector {
this.port = 8081
}
}
return embeddedServer(factory, environment)
}
}
Above is what I tried, but gives below error. And I don't have onCreate on this non-activity class.
java.lang.IllegalStateException: LifecycleOwner com.youtap.upti.MainActivity#38dcf06 is attempting to register while current state is RESUMED. LifecycleOwners must call register before they are STARTED.
Any suggestions to resolve this problem would be grateful.
Below same above snippet as a screenshot to display helper text on declaration/param types from Android Studio:
And I invoke this server class from onCreate() of MainActivity:
To solve your problem and to hide the complexity you can create an intermediate class for launching activity and waiting for a result to come:
import kotlinx.coroutines.channels.Channel
class Repository(private val activity: MainActivity) {
private val channel = Channel<Int>(1)
suspend fun get(input: String): Int {
activity.activityLauncher.launch(input)
return channel.receive()
}
suspend fun callback(result: Int) {
channel.send(result)
}
}
You can store a reference to a repository and an activity launcher in the MainActivity class:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
CoroutineScope(Dispatchers.IO).launch {
HttpServer(this#MainActivity).also { it.start() }
}
}
val activityLauncher = registerForActivityResult(MySecondActivityContract()) { result ->
GlobalScope.launch {
repository.callback(result!!)
}
}
val repository = Repository(this)
}
My second activity and a contract looks like the following:
class ChildActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_child)
val result = Intent()
result.putExtra("name", 6666)
result.data = Uri.parse("http://mydata")
setResult(Activity.RESULT_OK, result)
finish()
}
}
class MySecondActivityContract : ActivityResultContract<String, Int?>() {
override fun createIntent(context: Context, input: String?): Intent {
return Intent(context, ChildActivity::class.java)
.putExtra("my_input_key", input)
}
override fun parseResult(resultCode: Int, intent: Intent?): Int? = when {
resultCode != Activity.RESULT_OK -> null
else -> intent?.getIntExtra("name", 42)
}
override fun getSynchronousResult(context: Context, input: String?): SynchronousResult<Int?>? {
return if (input.isNullOrEmpty()) SynchronousResult(42) else null
}
}
The most simplest part is routing handler:
routing {
route("/") {
post {
val result = (applicationContext as MainActivity).repository.get("input")
call.respondText { result.toString() }
}
}
}
This solution works but only one request is processed at the same time and it's not robust because Activity may be destroyed before HTTP server or repository objects.
so I have an NFC tag that stores a URL and I am building an android app that reads that tag and starts the app to the Main activity. After that I have a WebView that displays that website. My problem here is, each tag has a different url/path (lets say they are clothing items and each point to a product on the website). How do I get the NDEF message from the tag (the url) and pass it to that parameter of the webview? Thank your for your help, here I will leave my code.
I have followed the android documentation on NFC both, basic and advanced, googled every page and searched everything on reddit, however I could not find any answer...
This is my Main Activity, the one that opens as soon as it reads the tag:
class MainActivity : AppCompatActivity() {
private var adapter: NfcAdapter? = null
// Pending intent for NFC intent foreground dispatch.
// Used to read all NDEF tags while the app is running in the foreground.
private var nfcPendingIntent: PendingIntent? = null
// Optional: filter NDEF tags this app receives through the pending intent.
//private var nfcIntentFilters: Array<IntentFilter>? = null
private val KEY_LOG_TEXT = "logText"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
initNfcAdapter()
if (intent != null)
{
processIntent(intent)
}
}
private fun initNfcAdapter() {
val nfcManager = getSystemService(Context.NFC_SERVICE) as NfcManager
adapter = nfcManager.defaultAdapter
}
override fun onResume() {
super.onResume()
enableNfcForegroundDispatch()
}
private fun enableNfcForegroundDispatch() {
try {
val intent = Intent(this, javaClass).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
val nfcPendingIntent = PendingIntent.getActivity(this, 0, intent, 0)
adapter?.enableForegroundDispatch(this, nfcPendingIntent, null, null)
} catch (ex: IllegalStateException) {
}
}
override fun onPause() {
disableNfcForegroundDispatch()
super.onPause()
}
private fun disableNfcForegroundDispatch() {
try {
adapter?.disableForegroundDispatch(this)
} catch (ex: IllegalStateException) {
}
}
private fun processIntent(checkIntent: Intent) {
if(checkIntent.action == NfcAdapter.ACTION_NDEF_DISCOVERED) {
val rawMessages = checkIntent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES)
}
}
}
This is the webview activity:
class Webview : AppCompatActivity() {
private lateinit var webview: Webview1
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_webview)
val myWebView: WebView = findViewById(R.id.webview)
myWebView.webViewClient = object : WebViewClient () {
override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean {
if (url != null) {
view?.loadUrl(url)
}
return true
}
}
myWebView.loadUrl("https://website.com")
myWebView.settings.javaScriptEnabled=true
myWebView.settings.allowContentAccess=true
myWebView.settings.domStorageEnabled=true
myWebView.settings.useWideViewPort=true
myWebView.settings.setAppCacheEnabled(true)
}
}
I want to pass the NDEF message to myWebVIew.loadUrl().
I would like to open a new activity when phoneViewModel and ScanViewModel are instantiated. They are instantiated by calling an async function InitialRead(). I'm logging each step, atm they are logged as done3 => done2 => done1
I would like to have them in this order:
done1 => done2 => done3
I have following code:
class MainBusinessActivity : AppCompatActivity() {
private lateinit var scanViewModel: ScanViewModel
private lateinit var phoneViewModel: PhoneViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main_business)
}
private fun startEntitySetListActivity() = GlobalScope.async {
val sapServiceManager = (application as SAPWizardApplication).sapServiceManager
sapServiceManager?.openODataStore {
phoneViewModel = ViewModelProvider(this#MainBusinessActivity).get(PhoneViewModel::class.java).also {it.initialRead{Log.e("done", "done1")}}
scanViewModel = ViewModelProvider(this#MainBusinessActivity).get(ScanViewModel::class.java).also {it.initialRead{Log.e("done", "done2")}}
}
}
override fun onResume() {
super.onResume()
//startEntitySetListActivity()
runBlocking {
startEntitySetListActivity().await()
val intent = Intent(this#MainBusinessActivity, HomeActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
Log.e("done", "done3")
startActivity(intent)
}
}
}
What am I doing wrong? Can someone correct my code?
Never use runBlocking in an Android app. runBlocking completely defeats the purpose of using coroutines, and can lead to an ANR. You also probably should never use GlobalScope, which leads to UI leaks. You might possibly need it for some kind of long-running task that doesn't make sense to put in a service but doesn't have dependency on any UI components, but I can't think of any examples
You also shouldn't be instantiating your ViewModels in the background. That should be done in onCreate().
Make this function a suspend function, and it can break down the two tasks in the background simultaneously before returning.
Start your coroutine with lifecycleScope.
Assuming sapServiceManager?.openODataStore is an asynchronous task that takes a callback, you will need to wrap it in suspendCoroutine.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main_business)
phoneViewModel = ViewModelProvider(this#MainBusinessActivity).get(PhoneViewModel::class.java)
scanViewModel = ViewModelProvider(this#MainBusinessActivity).get(ScanViewModel::class.java)
}
private suspend fun startEntitySetListActivity() = coroutineScope {
val sapServiceManager = (application as SAPWizardApplication).sapServiceManager
sapServiceManager ?: return
suspendCoroutine<Unit> { continuation ->
sapServiceManager.openODataStore { continuation.resume(Unit) }
}
listOf(
launch {
phoneViewModel.initialRead{Log.e("done", "done1")}
},
launch {
scanViewModel.initialRead{Log.e("done", "done2")}
}
).joinAll()
}
override fun onResume() {
super.onResume()
lifecycleScope.launch {
startEntitySetListActivity()
val intent = Intent(this#MainBusinessActivity, HomeActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
Log.e("done", "done3")
startActivity(intent)
}
}
I'm trying to make the basic ZXing functionality to scan qr codes using their basic instructions, but my camera does not open it just goes to the blank ScanActivity.
I've already added the "implementation" on module app dependency
implementation 'me.dm7.barcodescanner:zxing:1.9'
and permission on AndroidManifest.xml
<uses-permission android:name="android.permission.CAMERA"/>
I've also manually allowed its permission on the settings of the android phone i'm testing it on
My main activity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
tvResult = tvresult
btn.setOnClickListener {
val intent = Intent(this#MainActivity, ScanActivity::class.java)
startActivity(intent)
}
}
companion object {
var tvResult: TextView? = null
}
}
ScanActivity Class
class ScanActivity : AppCompatActivity(), ZXingScannerView.ResultHandler {
private var mScannerView: ZXingScannerView? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mScannerView = ZXingScannerView(this)
setContentView(R.layout.activity_scan)
mScannerView!!.setResultHandler(this) // Register ourselves as a handler for scan results.
}
public override fun onResume() {
super.onResume()
mScannerView!!.setResultHandler(this) // Register ourselves as a handler for scan results.
mScannerView!!.startCamera() // Start camera on resume
}
public override fun onPause() {
super.onPause()
mScannerView!!.stopCamera() // Stop camera on pause
}
override fun handleResult(rawResult: Result) {
// Do something with the result here
// Log.v("tag", rawResult.getText()); // Prints scan results
// Log.v("tag", rawResult.getBarcodeFormat().toString()); // Prints the scan format (qrcode, pdf417 etc.)
MainActivity.tvResult!!.setText(rawResult.text)
onBackPressed()
// If you would like to resume scanning, call this method below:
//mScannerView.resumeCameraPreview(this);
}}
after i click on the Scan barcode button it just goes to this
(im expecting the camera will open because of the startCamera()
I had a similar issue, it was permission related.
Try adding the following within the onCreate(..)
if (ContextCompat.checkSelfPermission(this#MainActivity, Manifest.permission.CAMERA) == PackageManager.PERMISSION_DENIED) {
ActivityCompat.requestPermissions(this#MainActivity, arrayOf(Manifest.permission.CAMERA), 123)
}
Late answer, by the way if someone else has this problem, you forgot to add mScannerView in your Layout