Dylon Jaynes
Dylon Jaynes

Reputation: 176

How to apply Clean Architecture when my data source is coming from a BluetoothLE Bound Service?

I have been developing this Android app for some time and have realized that my Activities are way too bloated so I have been trying to switch to MVVM with Clean Architecture but am running into an issue. I have this BluetoothLe service that provides the data to my application and it seems like I can only bind to my service from my Activities which is a problem because I am trying to separate the presentation from the data layer.

Here is some code from my activity which uses the service:

@AndroidEntryPoint
class DeviceActivity: AppCompatActivity() {

   ...

    override fun onCreate(savedInstanceState: Bundle?) {
        // No NightMode allowed.
        AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
        super.onCreate(savedInstanceState)

        binding = ActivityDeviceBinding.inflate(layoutInflater)
        val view = binding.root
        setContentView(view)

        setSupportActionBar(binding.myToolbar)

        if (intent.extras!!.get("btDeviceName") != null) {
            deviceName = intent.extras!!.get("btDeviceName").toString()
            binding.deviceTitle.text = deviceName
        }

        setupActionBar()

        // Binding to the service via gattServiceIntent.
        val gattServiceIntent = Intent(this, BluetoothLeService::class.java)
        bindService(gattServiceIntent, serviceConnection, Context.BIND_AUTO_CREATE)

        ...
    }

// Code to manage Service lifecycle.
    private val serviceConnection: ServiceConnection = object : ServiceConnection {
        override fun onServiceConnected(componentName: ComponentName?, service: IBinder?) {
            bluetoothService = (service as BluetoothLeService.LocalBinder).getService()
            bluetoothService?.let { bluetooth ->
                // Call functions on service to check connection and connect to devices
                if (!bluetooth.initialize()) {
                    Log.e("blah", "Unable to initialize Bluetooth.")
                    finish()
                }

                // Perform device connection
                if (bluetoothDevice != null) {
                    bluetooth.connect(bluetoothDevice!!)
                }
            }
        }

        override fun onServiceDisconnected(name: ComponentName?) {
            bluetoothService = null
        }
    }

Then I listen for the data via update receiver:

// Connect to device after registering for the receiver.
    private fun registerBluetoothLeServiceReceiver() {
        try {
            registerReceiver(gattUpdateReceiver, makeGattUpdateIntentFilter())
            if (bluetoothService != null) {
                connectToBluetoothDevice()
            }
        } catch (e: Exception) {
            Log.d(TAG, "onResume: $e Couldn't register receiver.")
        }
    }


private val gattUpdateReceiver: BroadcastReceiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context, intent: Intent) {
            when (intent.action) {
                BluetoothLeService.ACTION_GATT_CONNECTED -> {
                    connected = true
                    updateConnectionState(context.resources.getString(R.string.connected))
                }
                BluetoothLeService.ACTION_GATT_DISCONNECTED -> {
                    connected = false
                    updateConnectionState(context.resources.getString(R.string.disconnected))
                    leaveActivity()
                    binding.progressBarCyclic.visibility = GONE
                }
                // When services are discovered on the device, we request gatt to update the mtu size.
                BluetoothLeService.ACTION_MTU_UPDATED -> {
                    bluetoothService?.enableNotification()
                }
                // We write the characteristic only when notifications have been enabled.
                BluetoothLeService.ACTION_NOTIFICATION_ENABLED -> {
                    bluetoothService?.writeGattCharacteristic()
                }
                // This is where the data stream is received.
                BluetoothLeService.ACTION_DATA_AVAILABLE -> {
                    val byteArr = intent.getByteArrayExtra("byteArray")
                    Log.d(TAG, "onReceive: ${byteArr.contentToString()}")
                    if (byteArr != null) {
                        // Firmware versions greater than 3634 have different data streams.
                        binding.recyclerviewDevices.visibility = View.VISIBLE
                        if (isLessThan3634 && boardVersion == 2.1) {
                            dataProcessor.processData(byteArr)
                        } else {
                            dataProcessor.processData(byteArr, 1)
                        }
                    }
                }
            }
        }
    }

After this is a bunch of data manipulation and processing which I would much rather do in my ViewModel or another class for separation purposes.

Is there a way that I can get my service to bind to a repository or something that will allow me to separate the presentation and data layers so I can listen for data in something other than my Activity?

Upvotes: 0

Views: 1161

Answers (1)

Mahmoud Gamal El-Din
Mahmoud Gamal El-Din

Reputation: 91

I think you will need to add a new android module and name it "device" that implements your Bluetooth datasource and inject this module in your data layer. This article could help you

https://five.agency/android-architecture-part-1-every-new-beginning-is-hard/

Upvotes: 2

Related Questions