How to architecturally implement the HealthConnect Client inside a helperclass? - android

Im trying to implement the HealthConnect utilities inside a helperclass that handles the object usage, permission handling and availability of Health Connect on the users phone, however I have problems to find the right architecture that handles all of this in an elegant way. Here is the Code of the helperclass, HealthConnectManager.kt :
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContract
import androidx.core.content.ContextCompat.startActivity
import androidx.health.connect.client.HealthConnectClient
import androidx.health.connect.client.PermissionController
import androidx.health.connect.client.permission.HealthPermission
import androidx.health.connect.client.records.HeartRateRecord
import androidx.health.connect.client.records.StepsRecord
class HealthConnectManager(val context: Context) {
private val healthConnectClient by lazy { HealthConnectClient.getOrCreate(context) }
// build a set of permissions for required data types
val PERMISSIONS =
setOf(
HealthPermission.getReadPermission(HeartRateRecord::class),
HealthPermission.getWritePermission(HeartRateRecord::class),
HealthPermission.getReadPermission(StepsRecord::class),
HealthPermission.getWritePermission(StepsRecord::class)
)
init {
checkAvailability()
}
private fun checkAvailability() {
if (HealthConnectClient.isProviderAvailable(context)) {
// Health Connect is available and installed.
healthConnectClient = HealthConnectClient.getOrCreate(context)
} else {
Toast.makeText(
context, "Health Connect is not available", Toast.LENGTH_SHORT
).show()
val uri = Uri.parse("market://details?id=com.google.android.apps.healthdata")
val gpIntent = Intent(Intent.ACTION_VIEW, uri)
context.startActivity(gpIntent)
}
}
suspend fun hasAllPermissions(permissions: Set<HealthPermission>): Boolean {
return permissions == hcClient.permissionController.getGrantedPermissions(
permissions
)
}
fun requestPermissionsActivityContract(): ActivityResultContract<Set<HealthPermission>, Set<HealthPermission>> {
return PermissionController.createRequestPermissionResultContract()
}
}
My plan is to make an HealthConnectManagerObject in the places where I need Healthconnect, for example in the Mainactivity:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val hcManager = HealthConnectManager(this)
}
}
Now I have several Problems. How should I instantiate HealthConnectClient, only if HealthConnect is installed (see the function checkAvailability) In my code, I instantiate it twice, once as a class member and once in said function.
Another problem I cant really solve is Permission handling. The functions requestPermissionsActivityContract() and hasAllPermissions dont work in this helper class, partly because the syntax isnt 100% correct yet (its from the documentation) and partly because it needs to be called in an Activity, which I dont have in this Class. Should I give this Class in the Constructor an Activity additionaly to the context?
I tried to call the hasAllPermissions and requestPermissionsActivityContract() on the context which is given to this class, however it needs an Activity. Im not sure how to implement that. The Problem is that there isnt a lot of references on health connect, because its still in beta (The documentation isnt really helpful in this case)
If you have a better suggestion how to implement this I would gladly take it.
Thank you in advance

You can always instantiate your manager-instance regardless if the health connect app is installed on your user's device. The HC SDK functions separately from the inner-workings of the application itself. The instance of the HC SDK that resides within your app primarily communicates with the HC App via IPC. Theoretically, you can have a HealthConnectClient variable declared anywhere, you just need to make sure that HC is available via checkAvailability before calling any of its other method calls. If HC is unavailable, handle those cases gracefully and reflect the state on user's UI.
As far as architecture goes, make your HealthConnectManager singleton. Take advantage of dependency injection libraries like Hilt&Dagger for Android : https://developer.android.com/training/dependency-injection/hilt-android

Related

How To Record Calls In Android Devices Using Kotlin?

I want to make an Android application that can record both incoming and outgoing calls in the background as a service in kotlin and at a particular time in the day, it sends all that recordings to a server by API. I had researched about it all I found is to use Device Policy Manager and Telephoney Manager but it is not much about it on the internet. So can you help me with any article, documentation, or tutorial?
There is no solution from Google as of now. Google has deprecated the feature of recording the calls in it's latest versions of Android OS. Earlier it was possible, I had tried various methods but I was only getting the silent audio when I had tried to record calls. When using Google's Phone application it only allows that application to use the microphone and other things it won't allow any other application to overpower and get that hardware access.
But there are actually two hacks to do that.
Build your own phone application like Truecaller and manage every call and other things from that application by doing this you can get access to managing calls on your device and you will also get the access to record the calls.
If your work is specific to any one mobile example like Samsung, OnePlus, etc. Then you can use any Truecaller or Google's Phone application which will store the recordings of the calls in file storage and then you can make a service to upload that call recording from that particular file location every night at 12 AM or something.
first create MyCallRecordReceiver class
class MyCallRecordReceiver(callRecord: CallRecord) : CallRecordReceiver(callRecord) {
override fun onIncomingCallReceived(context: Context, number: String?, start: Date) {
super.onIncomingCallReceived(context, number, start)
}
}
then in MainActivity
class MainActivity : AppCompatActivity() {
companion object {
private val TAG = MainActivity::class.java.simpleName
}
private lateinit var callRecord: CallRecord
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
callRecord = CallRecord.Builder(this)
.setLogEnable(true)
.setRecordFileName("CallRecorderTestFile")
.setRecordDirName("CallRecorderTest")
.setAudioSource(MediaRecorder.AudioSource.VOICE_COMMUNICATION)
.setShowSeed(true)
.build()
}
fun StartCallRecordClick(view: View) {
LogUtils.i(TAG, "StartCallRecordClick")
callRecord.startCallReceiver()
}
fun StopCallRecordClick(view: View) {
LogUtils.i(TAG, "StopCallRecordClick")
callRecord.stopCallReceiver()
}
}
In addition Add it as a dependency in your app's build.gradle file
allprojects {
repositories {
maven { url 'https://jitpack.io' }
}
}
and this
compile 'com.github.aykuttasil:CallRecorder:1.5.3'

GetFusedLocationProviderClient returning null latitude and longitude values for users' location

For an app I'm making, I need to get the users' location which will then be displayed on the map.
To achieve this, I am using LocationServices.getFusedLocationProviderClient() in the code below.
When I get the location value in addOnSuccessListener, the latitude and longtitude values are null for some reason, so I'm getting a NullPointerException:
import android.Manifest
import android.annotation.SuppressLint
import android.location.Location
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.core.app.ActivityCompat
import com.google.android.gms.location.FusedLocationProviderClient
import com.google.android.gms.location.LocationServices
import com.google.android.gms.maps.CameraUpdateFactory
import com.google.android.gms.maps.GoogleMap
import com.google.android.gms.maps.OnMapReadyCallback
import com.google.android.gms.maps.SupportMapFragment
import com.google.android.gms.maps.model.LatLng
import com.google.android.gms.maps.model.MarkerOptions
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.realtomjoney.mapsapp.databinding.ActivityMainBinding
import java.lang.NullPointerException
class MainActivity : AppCompatActivity(), OnMapReadyCallback {
private lateinit var binding: ActivityMainBinding
lateinit var myMap: GoogleMap
lateinit var fusedLocation: FusedLocationProviderClient
#SuppressLint("MissingPermission")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
ActivityCompat.requestPermissions(this, arrayOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION) , 100)
val mapFragment = supportFragmentManager
.findFragmentById(R.id.map) as SupportMapFragment
mapFragment.getMapAsync(this)
binding.button2.setOnClickListener {
fusedLocation = LocationServices.getFusedLocationProviderClient(this)
fusedLocation.lastLocation.addOnSuccessListener { location: Location? ->
val latLong = LatLng(location!!.latitude, location.longitude)
myMap.addMarker(MarkerOptions().position(latLong).title("Found Location"))
myMap.moveCamera(CameraUpdateFactory.newLatLngZoom(latLong, 10f))
}
}
}
override fun onMapReady(p0: GoogleMap) {
myMap = p0
}
}
When I run the app, I get the following exception:
2021-10-19 16:46:09.859 8556-8556/com.realtomjoney.mapsapp E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.realtomjoney.mapsapp, PID: 8556
java.lang.NullPointerException
at com.realtomjoney.mapsapp.MainActivity.onCreate$lambda-1$lambda-0(MainActivity.kt:46)
at com.realtomjoney.mapsapp.MainActivity.$r8$lambda$wwcxhtPyugLtHLn65vCEt3JY33Q(Unknown Source:0)
at com.realtomjoney.mapsapp.MainActivity$$ExternalSyntheticLambda1.onSuccess(Unknown Source:4)
at com.google.android.gms.tasks.zzn.run(com.google.android.gms:play-services-tasks##17.2.0:4)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loopOnce(Looper.java:201)
at android.os.Looper.loop(Looper.java:288)
at android.app.ActivityThread.main(ActivityThread.java:7842)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1003)
And yes, I have said yes to all permissions when I ran the app for the first time.
If this question is poorly written or a duplicate, please let me know as I strive to create good and well-structured questions.
I have faced this isue as well especially when i first install my app on a device. there are two things that you can try out that i think will help:
one is trying to open the google maps app and waiting for it to get a fix and then go on and open your app. this should provide your fused location provider client a last location that it can look up. check this answer for reference
the second is going a step further and requesting location updates using the provider. If you only need a single location fix you can then go on and deregister it once you get the first one. I am doing this for my app and usually last location is only null the first time i use the app after installation. after that it usually works. this is probably what you should do anyway as you cant expect your users to turn on google maps before using your app.
and just to be safe, be aware that fusedlocationproviderclient sometimes stops working or behaves weirdly when you have pending google services updates on your device. so go ahead and make sure your device is up to date in this regard.
lastLocation can be null for several reasons.
They are listed here https://developer.android.com/training/location/retrieve-current#last-known
First of all, the correct procedure not to get null when initializing user coordinates to map is to initialize the map on onConnected() callback instead of onCreate() in an activity or fragment, lastLocation can be null.
You can use REQUEST_CODE_ASK_PERMISSIONSto handle the callback if not using location observer, the location observer basically would look something like this
if (location.provider == LocationManager.GPS_PROVIDER) {
getData()
}
I had the same case back in the days, my code was something like this on permission callback :
if (isGranted()) {
try {
fusedLocationProviderClient.lastLocation.addOnCompleteListener {
if (it.isSuccessful) {
if (it.result != null) {
setLocationData(it.result)
}
}
}
} catch (e: Exception) {
}
Basically on Permission granted, it will get your location and you can choose it whether you wanna use it to addMarker or anything else.
I had the same issue
check if the gps is turned on on the device
may be because of the billing of the google maps api key you are using
check all of this some times the error log shows the problem of google maps billing issue
It looks like you request the permission(ActivityCompat.requestPermissions(...)) but never handle the permission request(you need to override onRequestPermissionsResult(...)). More info on that: https://developer.android.com/training/permissions/requesting#manage-request-code-yourself
Or you can switch to the new method to allow the system to manage the request code: https://developer.android.com/training/permissions/requesting#allow-system-manage-request-code
One last thing, in some cases getLastLocation() can return null, in that case I call getCurrentLocation() which always returns new fresh device location, more info here: https://developer.android.com/training/location/retrieve-current#BestEstimate

How to restart Android app within Espresso test?

I am using Espresso with Kotlin for the UI test automation. I am trying to find a proper way to restart the app during the test and start it again, so the test scenario is the following:
start the app, go to login page
force close the app and open it again (basically restart it)
check some stuff etc
The way our UI tests are organized:
there is a test class where I have rules
val intent = Intent(ApplicationProvider.getApplicationContext(), MainActivity::class.java)
.putExtra(UI_TEST_INTENT, true)
#get:Rule
val rule = ActivityScenarioRule<MainActivity>(intent)
there Before/After functions and tests functions in this class
What I want is to have generic restartApp function in separated class, let's say TestUtils and to be able to call it at any point of time, when is needed.
So far I didn't find a solution. There are some similar questions on stackoverflow, but I am not sure I understand how to work with the answers I found, like this:
with(activityRule) {
finishActivity()
launchActivity(null)
}
Since ActivityTestRule is deprecated and documentation asking to use ActivityScenarioRule, I tried this:
#get:Rule
val rule = ActivityScenarioRule<MainActivity>(intent)
private fun restart() {
rule.scenario.close()
rule.scenario.recreate()
}
but it gets java.lang.NullPointerException
another option is
private fun restart() {
pressBackUnconditionally()
Intents.release()
ActivityScenario.launch<MainActivity>(intent)
}
it works, app restarts but I can not interact with the app anymore, because for some reason there are two intents running now
Would be great to get an answer I can work with (I am quite new to Espresso)
Cheers
The solution is found:
private fun restart() {
Intents.release()
rule.scenario.close()
Intents.init()
ActivityScenario.launch<MainActivity>(intent)
}
Seems like the author's answer has some excess code. The following is enough
activityScenarioRule.scenario.close()
ActivityScenario.launch(YourActivity::class.java, null)

Why does my app crash when helper class is instantiated?

I'm building a Unity game that uses a native android library with one activity. This library includes bluetooth functionality from an sdk for which i created a Helper class that i can't instantiate or reference in any way in my MainActivity without the game crashing.
I'm new to Kotlin and android studio and can't figure out how to fix this.
Building & running in android and unity works without any errors.
Running an android native app set up in the same way like the android library, works flawless.
But when running the game independantly on phone it crashes, this has been tested on multiple devices.
MainActivity snippet
import com.X.unitylib.BluetoothDeviceHelper //this import is not used for some reason
class MainActivity : UnityPlayerActivity ()/*, BluetoothDeviceHelper.Listener*/ {
private lateinit var bluetoothDeviceHelper: BluetoothDeviceHelper // App runs with this
//private var bluetoothDevices = emptyList<BluetoothDeviceHelper.BluetoothDevice>() //Doesn't run
//private var connectedBluetoothDevice: BluetoothDeviceHelper.BluetoothDevice? = null //Doesn't run
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
bluetoothDeviceHelper = BluetoothDeviceHelper(applicationContext) //App doesn't run with this uncommented
}
/* override fun onStart() {
super.onStart()
//bluetoothDeviceHelper.addListener(this)
bluetoothDeviceHelper.startScan()
}*/
**BluetoothDeviceHelper snippet**
class BluetoothDeviceHelper(context: Context) : BLEDevice.Delegate<BLEDevice>, BLEManager.Delegate<BLEObject>
Edit: was able to fix a couple issues so i updated my initial description and replaced the outdated stack trace with the latest error.
Edit2: New error, on monday i'll try the steps described in the link below and once i get it working, i'll post an answer with all the steps i took for others that end up in the same fringe situation.
Kotlin and Unity development error

Android - display In-app message natively

I would like my app tз display a notification of the incoming messages, but only when the application is active, similar to how many social apps do that.
I.e., the user has my messaging app open and he gets a notification slide in from top, within this Android application.
To my understanding, this is something that is called “in app messages” in Firebase.
However, I wouldn’t like to have firebase as a dependency, as I am not using any part of it: the notifications will be triggered by an open network connection that my app made.
I also so wouldn’t want to involve push notifications as I need this functionality only when the app is active.
What would be the best way to achieve this goal?
Basically what I am asking is how to make my own notification “bubble” in UI that shows up inside my app, similar to how it is done in messaging/dating apps (see Badoo, for example). Mainly I am wondering if there are any implemendations available that I could use or do I have to draw this stuff myself (using Fragments?)
It's a very broad question. So in broad strokes: Use some real time communications technology, such as sockets/websockets to listen for incoming messages, and hook up into lifecycle to start listening when the app moves into foreground (and stop when it moves out) [assuming that is the meaning of app being active - otherwise if you include foreground state, just start listening and don't unlisten) -
class MyListener : LifecycleObserver {
#OnLifecycleEvent(Lifecycle.Event.ON_START)
fun onMoveToForeground() {
listenForNotification()
//start listening
}
#OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun onMoveToBackground() {
//stop listening
}
where listener would be something like this:
private suspend fun listenForNotification(){
withContext(Dispatchers.IO){
myApi.receive() {
println("this is my notification object: $it")
NotificationHelper.sendNotification($it.message)
}
}
}
And NotificationHelper would be based on Notification Manager to push local notifications (as you wanted them to slide from the top - look like any push notification). Pay close attention to the flags you use to send the notification to make sure it is received and processed by the currently opened activity (do more research on it, separate topic) https://developer.android.com/reference/android/app/NotificationManager
In that same activity use OnNewIntent to receive user's action of tapping on the notification and then do whatever you want to do with it.
Alternatively, do not use local notification but just develop your own UI where you would display these things messaging style. (edit: for example, like this - link. Another one for actually showing notifications without using Notifications lib -link
Or a combination of both local notifications and the above example.
Edit:
*You can also use Firebase messaging to display messages locally.* Yes you would still need a firebase json to init the app, but after that you can construct your messages locally and display them, so it a very lightweight dependency on two libs and aside from initializing you won't need anything else from the firebase server.
Below is an example with two types of messages, card and banner. And of course you can just take the full code on GitHub and extract the part you need and modify it as needed. (the method used here is public for testing the appearance of the message locally - I don't see anything wrong with using it as a vehicle to deliver local notifications, but again the option to take the code and modify is always there)
import android.content.Intent
import android.net.Uri
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.google.firebase.FirebaseApp
import com.google.firebase.inappmessaging.MessagesProto
import com.google.firebase.inappmessaging.display.FirebaseInAppMessagingDisplay
import com.google.firebase.inappmessaging.model.*
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
companion object {
val whiteHex = "#ffffff"
val magHex = "#9C27B0"
val appUrl ="app://open.my.app"
}
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
val action: String? = intent?.action
val data: Uri? = intent?.data
data?.let {
helloTextView.text ="You just clicked from Firebase Message"
return
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
FirebaseApp.initializeApp(this)
val text = Text.builder()
.setHexColor(whiteHex)
.setText("Local Firebase Message Body")
.build()
val title = Text.builder()
.setHexColor(whiteHex)
.setText("Local Firebase Message Title")
.build()
val imageData = ImageData.builder()
.setImageUrl("https://homepages.cae.wisc.edu/~ece533/images/frymire.png")
.build()
val button = Button.builder()
.setButtonHexColor(whiteHex).setText(text).build()
val campaignMeta = CampaignMetadata("S", "D", true)
val primaryAction = Action.builder()
.setActionUrl(appUrl)
.setButton(button)
.build()
val fmessage = CardMessage.builder()
.setPrimaryAction(primaryAction)
.setBackgroundHexColor(magHex)
.setPortraitImageData(imageData)
.setTitle(title).build(campaignMeta)
val bannerMessage = BannerMessage.builder()
.setAction(primaryAction)
.setImageData(imageData)
.setBackgroundHexColor(magHex)
.setBody(text)
.setTitle(title).build(campaignMeta)
FirebaseInAppMessagingDisplay
.getInstance()
.testMessage(this, bannerMessage, null)
}
}
In build.gradle make sure to add:
implementation 'com.google.firebase:firebase-core:17.2.1'
implementation 'com.google.firebase:firebase-inappmessaging-display:19.0.2'
and intent filter into manifest (to process click on the message)
<data android:scheme="app" android:host="open.my.app" />
also modify launchMode to singleTop to process the click within the same instance of the activity:
<activity android:name=".MainActivity"
android:launchMode="singleTop"
>
and apply
apply plugin: 'com.google.gms.google-services'
The result:
Card message:
Banner message and updating text in response to clicking on the banner:
Added project into GitHub if you are interested - project link. Must add your own google-services.json for firebase (to be able to init the engine only)

Categories

Resources