Home » Android » android – Ble with MVVM and Architecture Components, proper way?

android – Ble with MVVM and Architecture Components, proper way?

Posted by: admin May 14, 2020 Leave a comment

Questions:

I’m working with Bluetooth LE devices and I was thinking about my current approach and best practices. Currently I have an activity which handles the connection and the GattCallbacks and I decided to restructure the code to get an better overview and maintainability cause its quite messy actually.

I found the BleManager from NordicSemiconductor https://github.com/NordicSemiconductor/Android-BLE-Library/

It’s an abstraction of the basic steps for connecting with a BLE device and it handles the GattCallbacks + providing an appropriate interface to use it from a service or a ViewModel.

I’d like to use the ViewModel approach but I’m not so familiar with MVC, MVP, MVVM patterns and there are some questions that I still can’t reply

This class is extending the BleManager (BlinkyManager.java)

It shows how to make use of the BleManager so I adopted the class and called it ECountBleManager.

EDIT:
The last 6 days I did reaearches especially facing the MVVM pattern and the Architecture Components. Unfortunately there are still a lot of questions that I can’t reply myself. But I really want to get better so I made a draft of my current concept. I hope you can help me answering my questions and improving my project.

I’m especially interested in best practices.

Here is my draft:

enter image description here

And here are my class implementations:
ECountActivity.java

public class ECountActivity extends AppCompatActivity {

    private ECountViewModel viewModel;

    @Override
    protected void onCreate(final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.detail_view);

        // hide magnifier icon
        GifImageView customLoader = findViewById(R.id.progressBar);
        customLoader.setVisibility(View.GONE);

        // Get additional data from previous activity
        final BluetoothDevice device = getIntent().getParcelableExtra("device");

        initViewModel();
        viewModel.connect(device);
    }

    private void initViewModel() {
        viewModel = ViewModelProviders.of(this).get(ECountViewModel.class);
        subscribeDataStreams(viewModel);
    }

    private void subscribeDataStreams(ECountViewModel viewModel) {
        viewModel.isDeviceReady().observe(this, deviceReady -> openOptionsFragment());


        viewModel.isConnected().observe(this, status -> {
            // Todo: ...
        });
    }

    private void openOptionsFragment() {
        // load options fragment
        FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
        ft.replace(R.id.contentFragment, new OptionsFragment());
        ft.commitNow();
    }

}

OtaFragment.java

public class OtaFragment extends Fragment implements FolderChooserDialog.FolderCallback,
        FileChooserDialog.FileCallback {

    private Button partialOtaButton;
    private Button fullOtaButton;
    private Button submitButton;
    private SeekBar mtuSeekBar;
    private EditText mtuInput;
    private LinearLayout stacklayout;
    private Button browseAppButton;
    private TextView folderPathText;
    private TextView appFileNameText;

    private MaterialDialog otaPrepareDialog;
    private MaterialDialog otaProgressDialog;

    private ECountViewModel viewModel;
    private OtaViewModel otaViewModel;


    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        initViewModel();
    }

    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // inflate the layout for this fragment
        View view = inflater.inflate(R.layout.ota_fragment, container, false);
        initViews(view);
        return view;
    }

    @Override
    public void onFolderSelection(@NonNull FolderChooserDialog dialog, @NonNull File folder) {
        final String otaFolderPath = folder.toString();
        otaViewModel.setOtaFolderPath(otaFolderPath);
        folderPathText.setText(otaFolderPath.substring(otaFolderPath.lastIndexOf("/")));
        // enable app browse
        browseAppButton.setClickable(true);
        browseAppButton.setEnabled(true);
    }

    @Override
    public void onFolderChooserDismissed(@NonNull FolderChooserDialog dialog) {}

    @Override
    public void onFileSelection(@NonNull FileChooserDialog dialog, @NonNull File file) {
        final String otaAppFilePath = file.toString();
        otaViewModel.setOtaAppFilePath(otaAppFilePath);
        appFileNameText.setText(otaAppFilePath.substring(otaAppFilePath.lastIndexOf("/")));
        // enable submitButton button
        submitButton.setClickable(true);
        submitButton.setEnabled(true);
    }

    @Override
    public void onFileChooserDismissed(@NonNull FileChooserDialog dialog) {}

    private void subscribeDataStreams(ECountViewModel viewModel) {
        viewModel.isOtaMode().observe(this, otaMode -> {
            otaPrepareDialog.dismiss();
            initOtaProgressDialog();
            otaProgressDialog.show();

            // Todo: how can i get mtu?
            viewModel.requestMtu(512);
        });
    }

    private void initViewModel() {
        viewModel = ViewModelProviders.of(getActivity()).get(ECountViewModel.class);
        otaViewModel = ViewModelProviders.of(getActivity()).get(OtaViewModel.class);
        subscribeDataStreams(viewModel);
    }

    private void initViews(View view) {
        // get resources
        final Button browseFolderButton = view.findViewById(R.id.browseFolder);
        final Button cancelButton = view.findViewById(R.id.ota_cancel);
        final SeekBar prioritySeekBar = view.findViewById(R.id.connection_seekBar);
        partialOtaButton = view.findViewById(R.id.radio_ota);
        fullOtaButton = view.findViewById(R.id.radio_ota_full);
        browseAppButton = view.findViewById(R.id.browseApp);
        folderPathText = view.findViewById(R.id.folderPathText);
        appFileNameText = view.findViewById(R.id.appFileNameText);
        stacklayout = view.findViewById(R.id.stacklayout);
        submitButton = view.findViewById(R.id.ota_proceed);
        mtuSeekBar = view.findViewById(R.id.mtu_seekBar);
        mtuInput = view.findViewById(R.id.mtu_value);

        // set initial states
        mtuSeekBar.setMax(512-23);
        mtuSeekBar.setProgress(244);
        prioritySeekBar.setMax(2);
        prioritySeekBar.setProgress(1);
        browseAppButton.setClickable(false);
        browseAppButton.setEnabled(false);
        submitButton.setClickable(false);
        submitButton.setEnabled(false);

        mtuInput.setOnEditorActionListener((v, actionId, event) -> {
            final Editable mtuText = mtuInput.getText();
            if (mtuText != null) {
                int mtu = Integer.valueOf(mtuText.toString());
                if (mtu < 23)  mtu = 23;
                if (mtu > 512) mtu = 512;
                mtuSeekBar.setProgress(mtu);
                viewModel.setMtu(mtu);
            }
            return false;
        });


        mtuSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {}

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {}

            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                mtuInput.setText(String.valueOf(progress));
                viewModel.setMtu(progress);
            }
        });

        prioritySeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener(){
            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {}

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {}

            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                viewModel.setPriority(progress);
            }
        });

        browseFolderButton.setOnClickListener(v -> new FolderChooserDialog.Builder(getActivity())
                .chooseButton(R.string.positiveTextChoose)
                .tag("#folder")
                .show(getChildFragmentManager()));

        browseAppButton.setOnClickListener(v -> new FileChooserDialog.Builder(getActivity())
                .initialPath(otaViewModel.getOtaFolderPath())
                .extensionsFilter(".ebl")
                .tag("#app")
                .show(getChildFragmentManager()));

        cancelButton.setOnClickListener(v -> Log.i("ota", "cancelButton"));

        submitButton.setOnClickListener(v -> {
            // disable OTA submitButton button
            submitButton.setClickable(false);
            submitButton.setEnabled(false);
            // init OTA process
            viewModel.initOtaMode();
            // show OTA preparing dialog
            otaPrepareDialog.show();
        });

        fullOtaButton.setOnClickListener(v -> {
            stacklayout.setVisibility(View.VISIBLE);
            partialOtaButton.setBackgroundColor(getResources().getColor(R.color.colorPrimary));
            fullOtaButton.setBackgroundColor(getResources().getColor(R.color.colorPrimaryDark));
        });

        partialOtaButton.setOnClickListener(v -> {
            stacklayout.setVisibility(View.GONE);
            partialOtaButton.setBackgroundColor(getResources().getColor(R.color.colorPrimaryDark));
            fullOtaButton.setBackgroundColor(getResources().getColor(R.color.colorPrimary));
        });

        otaPrepareDialog = new MaterialDialog.Builder(getActivity())
                .title(R.string.otaDialogHeaderText)
                .content(R.string.waiting)
                .progress(true, 0)
                .progressIndeterminateStyle(true)
                .build();

        otaProgressDialog = new MaterialDialog.Builder(getActivity())
                .title("test")
                .customView(R.layout.ota_progress2, false)
                .build();
    }

    private void initOtaProgressDialog() {
        // Todo: ...
    }
}

ECountViewModel.java

public class ECountViewModel extends AndroidViewModel implements ECountBleManagerCallbacks {
    private final ECountBleManager eCountBleManager;

    // Connection states Connecting, Connected, Disconnecting, Disconnected etc.
    private final MutableLiveData<String> connectionState = new MutableLiveData<>();

    // Flag to determine if the device is connected
    private final MutableLiveData<Boolean> isConnected = new MutableLiveData<>();

    // Flag to determine if the device is ready
    private final MutableLiveData<Void> onDeviceReady = new MutableLiveData<>();

    // Flag to determine if the device is in OTA mode
    private final MutableLiveData<Void> onOtaMode = new MutableLiveData<>();


    public LiveData<Void> isDeviceReady() {
        return onDeviceReady;
    }

    public LiveData<Void> isOtaMode() {
        return onOtaMode;
    }

    public LiveData<String> getConnectionState() {
        return connectionState;
    }

    public LiveData<Boolean> isConnected() {
        return isConnected;
    }

    public ECountViewModel(@NonNull final Application application) {
        super(application);

        // Initialize the manager
        eCountBleManager = new ECountBleManager(getApplication());
        eCountBleManager.setGattCallbacks(this);
    }

    /**
     * Connect to peripheral
     */
    public void connect(final BluetoothDevice device) {
        eCountBleManager.connect(device);
    }

    /**
     * Disconnect from peripheral
     */
    private void disconnect() {
        eCountBleManager.disconnect();
    }

    @Override
    protected void onCleared() {
        super.onCleared();
        if (eCountBleManager.isConnected()) {
            disconnect();
        }
    }

    @Override
    public void onDeviceConnecting(BluetoothDevice device) {
    }

    @Override
    public void onDeviceConnected(BluetoothDevice device) {
        isConnected.postValue(true);
    }

    @Override
    public void onDeviceDisconnecting(BluetoothDevice device) {
        isConnected.postValue(false);
    }

    @Override
    public void onDeviceDisconnected(BluetoothDevice device) {
        isConnected.postValue(false);
    }

    @Override
    public void onLinklossOccur(BluetoothDevice device) {
        isConnected.postValue(false);
    }

    @Override
    public void onServicesDiscovered(BluetoothDevice device, boolean optionalServicesFound) {
    }

    @Override
    public void onDeviceReady(BluetoothDevice device) {
        onDeviceReady.postValue(null);
    }

    @Override
    public void onOptionalServiceSupported(BluetoothDevice device) {
        onOtaMode.postValue(null);
    }

    @Override
    public void onBondingRequired(BluetoothDevice device) {

    }

    @Override
    public void onBonded(BluetoothDevice device) {

    }

    @Override
    public void onError(BluetoothDevice device, String message, int errorCode) {

    }

    @Override
    public void onDeviceNotSupported(BluetoothDevice device) {
        disconnect();
    }

    // delegate call from options fragment to ECountBleManager
    public String getDeviceId() {
        return BinaryUtils.byteArrayToHexString(eCountBleManager.getDeviceId());
    }

    // delegate call from ota fragment to ECountBleManager
    public void setMtu(final int value) {
        eCountBleManager.setMtu(value);
    }

    public void setPriority(final int value) {
        eCountBleManager.setPriority(value);
    }

ECountBleManager.java

public class ECountBleManager extends BleManager<BleManagerCallbacks> {
private static final String TAG = ECountBleManager.class.getSimpleName();
private final Handler handler;
private BluetoothGattCharacteristic authCharacteristic;
private BluetoothGattCharacteristic deviceIdCharacteristic;
private BluetoothGattCharacteristic deviceVersionCharacteristic;
private BluetoothGattCharacteristic configIdCharacteristic;
private BluetoothGattCharacteristic configTransmissionIntervalCharacteristic;
private BluetoothGattCharacteristic configKeepAliveIntervalCharacteristic;
private BluetoothGattCharacteristic configRadioModeCharacteristic;
private BluetoothGattCharacteristic configGpsCharacteristic;
private BluetoothGattCharacteristic configRadarCharacteristic;
private BluetoothGattCharacteristic configOperationModeCharacteristic;
private BluetoothGattCharacteristic configLoRaAppEuiCharacteristic;
private BluetoothGattCharacteristic configLoRaAppKeyCharacteristic;
private BluetoothGattCharacteristic configLoRaDeviceEuiCharacteristic;
private BluetoothGattCharacteristic operationCmdCharacteristic;
private BluetoothGattCharacteristic nemeusStatusCharacteristic;
private BluetoothGattCharacteristic gmrStatusCharacteristic;
private BluetoothGattCharacteristic radarStatusCharacteristic;
private BluetoothGattCharacteristic otaControlCharacteristic;
private BluetoothGattCharacteristic otaDataCharacteristic;
private byte[] configTransmissionInterval;
private byte[] configKeepAliveInterval;
private byte[] configRadioMode;
private byte[] configOperationMode;
private byte[] configId;
private byte[] deviceId;
private byte[] deviceVersion;
private byte[] configGps;
private byte[] configRadar;
private byte[] configLoRaAppEui;
private byte[] configLoRaAppKey;
private byte[] configLoRaDeviceEui;
private byte[] operationCmd;
private byte[] nemeusStatus;
private byte[] gmrStatus;
private byte[] radarStatus;
// OTA flags
private boolean isOtaProcessing = false;
private boolean isReconnectRequired = false;
private MutableLiveData<Boolean> isOtaMode = new MutableLiveData<>();
// OTA variables
private int mtu = 512;
private int priority = BluetoothGatt.CONNECTION_PRIORITY_HIGH;
private byte[] otaAppFileStream;
////////////////////////////
public ECountBleManager(Context context) {
super(context);
handler = new Handler();
}
@Override
protected BleManagerGattCallback getGattCallback() {
return gattCallback;
}
@Override
protected boolean shouldAutoConnect() {
return true;
}
/**
* BluetoothGatt callbacks for connection/disconnection, service discovery, receiving indication, etc
*/
private final BleManagerGattCallback gattCallback = new BleManagerGattCallback() {
@Override
protected void onDeviceReady() {
super.onDeviceReady();
}
@Override
protected void onOptionalServiceSupported() {
super.onOptionalServiceSupported();
isOtaMode.postValue(true);
}
@Override
protected boolean isOptionalServiceSupported(BluetoothGatt gatt) {
final BluetoothGattService otaService = gatt.getService(DC_UUID.otaService);
otaDataCharacteristic = otaService.getCharacteristic(DC_UUID.otaData);
return otaDataCharacteristic != null;
}
@Override
protected boolean isRequiredServiceSupported(BluetoothGatt gatt) {
final BluetoothGattService dcService = gatt.getService(DC_UUID.dcService);
final BluetoothGattService otaService = gatt.getService(DC_UUID.otaService);
if (dcService == null || otaService == null) return false;
authCharacteristic = dcService.getCharacteristic(DC_UUID.authentication);
deviceIdCharacteristic = dcService.getCharacteristic(DC_UUID.deviceId);
deviceVersionCharacteristic = dcService.getCharacteristic(DC_UUID.deviceVersion);
configIdCharacteristic = dcService.getCharacteristic(DC_UUID.configId);
configTransmissionIntervalCharacteristic = dcService.getCharacteristic(DC_UUID.configTransmissionInterval);
configKeepAliveIntervalCharacteristic = dcService.getCharacteristic(DC_UUID.configKeepAliveInterval);
configRadioModeCharacteristic = dcService.getCharacteristic(DC_UUID.configRadioMode);
configGpsCharacteristic = dcService.getCharacteristic(DC_UUID.configGps);
configRadarCharacteristic = dcService.getCharacteristic(DC_UUID.configRadar);
configOperationModeCharacteristic = dcService.getCharacteristic(DC_UUID.configOperationMode);
configLoRaAppEuiCharacteristic = dcService.getCharacteristic(DC_UUID.configLoRaAppEui);
configLoRaAppKeyCharacteristic = dcService.getCharacteristic(DC_UUID.configLoRaAppKey);
configLoRaDeviceEuiCharacteristic = dcService.getCharacteristic(DC_UUID.configLoRaDeviceEui);
operationCmdCharacteristic = dcService.getCharacteristic(DC_UUID.operationCmd);
nemeusStatusCharacteristic = dcService.getCharacteristic(DC_UUID.nemeusStatus);
gmrStatusCharacteristic = dcService.getCharacteristic(DC_UUID.gmrStatus);
radarStatusCharacteristic = dcService.getCharacteristic(DC_UUID.radarStatus);
otaControlCharacteristic = otaService.getCharacteristic(DC_UUID.otaControl);
return authCharacteristic != null &&
deviceIdCharacteristic != null &&
deviceVersionCharacteristic != null&&
configIdCharacteristic != null &&
configTransmissionIntervalCharacteristic != null &&
configKeepAliveIntervalCharacteristic != null &&
configRadioModeCharacteristic != null &&
configGpsCharacteristic != null &&
configRadarCharacteristic != null &&
configOperationModeCharacteristic != null &&
configLoRaAppEuiCharacteristic != null &&
configLoRaAppKeyCharacteristic != null &&
configLoRaDeviceEuiCharacteristic != null &&
operationCmdCharacteristic != null &&
nemeusStatusCharacteristic != null &&
gmrStatusCharacteristic != null &&
radarStatusCharacteristic != null &&
otaControlCharacteristic != null;
}
@Override
protected Deque<Request> initGatt(BluetoothGatt gatt) {
final LinkedList<Request> requests = new LinkedList<>();
requests.push(Request.readRequest(deviceIdCharacteristic));
requests.push(Request.readRequest(deviceVersionCharacteristic));
requests.push(Request.readRequest(configIdCharacteristic));
requests.push(Request.readRequest(configTransmissionIntervalCharacteristic));
requests.push(Request.readRequest(configKeepAliveIntervalCharacteristic));
requests.push(Request.readRequest(configRadioModeCharacteristic));
requests.push(Request.readRequest(configGpsCharacteristic));
requests.push(Request.readRequest(configRadarCharacteristic));
requests.push(Request.readRequest(configOperationModeCharacteristic));
requests.push(Request.readRequest(configLoRaAppEuiCharacteristic));
requests.push(Request.readRequest(configLoRaAppKeyCharacteristic));
requests.push(Request.readRequest(operationCmdCharacteristic));
requests.push(Request.readRequest(configLoRaDeviceEuiCharacteristic));
requests.push(Request.readRequest(nemeusStatusCharacteristic));
requests.push(Request.readRequest(gmrStatusCharacteristic));
requests.push(Request.readRequest(radarStatusCharacteristic));
// write authentication key to characteristic
requests.push(Request.writeRequest(authCharacteristic));
// perform server authentication
requests.push(Request.readRequest(authCharacteristic));
return requests;
}
@Override
protected void onDeviceDisconnected() {
authCharacteristic = null;
deviceIdCharacteristic = null;
deviceVersionCharacteristic = null;
configIdCharacteristic = null;
configTransmissionIntervalCharacteristic = null;
configKeepAliveIntervalCharacteristic = null;
configRadioModeCharacteristic = null;
configGpsCharacteristic = null;
configRadarCharacteristic = null;
configOperationModeCharacteristic = null;
configLoRaAppEuiCharacteristic = null;
configLoRaAppKeyCharacteristic = null;
configLoRaDeviceEuiCharacteristic = null;
nemeusStatusCharacteristic = null;
gmrStatusCharacteristic = null;
radarStatusCharacteristic = null;
otaDataCharacteristic = null;
}
@Override
protected void onMtuChanged(int mtu) {
super.onMtuChanged(mtu);
ECountBleManager.this.mtu = mtu;
}
@Override
protected void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
super.onCharacteristicRead(gatt, characteristic);
if (characteristic.getUuid().equals(DC_UUID.authentication)) {
byte encryptedData[];
try {
encryptedData = BinaryUtils.encryptByteArray(characteristic.getValue());
} catch (Exception e) {
e.printStackTrace();
return;
}
characteristic.setValue(encryptedData);
} else if (characteristic.getUuid().equals(DC_UUID.deviceId)) {
deviceId = characteristic.getValue();
} else if (characteristic.getUuid().equals(DC_UUID.deviceVersion)) {
deviceVersion = characteristic.getValue();
} else if (characteristic.getUuid().equals(DC_UUID.configId)) {
configId = characteristic.getValue();
} else if (characteristic.getUuid().equals(DC_UUID.configTransmissionInterval)) {
configTransmissionInterval = characteristic.getValue();
} else if (characteristic.getUuid().equals(DC_UUID.configKeepAliveInterval)) {
configKeepAliveInterval = characteristic.getValue();
} else if (characteristic.getUuid().equals(DC_UUID.configRadioMode)) {
configRadioMode = characteristic.getValue();
} else if (characteristic.getUuid().equals(DC_UUID.configGps)) {
configGps = characteristic.getValue();
} else if (characteristic.getUuid().equals(DC_UUID.configRadar)) {
configRadar = characteristic.getValue();
} else if (characteristic.getUuid().equals(DC_UUID.configOperationMode)) {
configOperationMode = characteristic.getValue();
} else if (characteristic.getUuid().equals(DC_UUID.configLoRaAppEui)) {
configLoRaAppEui = characteristic.getValue();
} else if (characteristic.getUuid().equals(DC_UUID.configLoRaAppKey)) {
configLoRaAppKey = characteristic.getValue();
} else if (characteristic.getUuid().equals(DC_UUID.configLoRaDeviceEui)) {
configLoRaDeviceEui = characteristic.getValue();
} else if (characteristic.getUuid().equals(DC_UUID.nemeusStatus)) {
nemeusStatus = characteristic.getValue();
} else if (characteristic.getUuid().equals(DC_UUID.gmrStatus)) {
gmrStatus = characteristic.getValue();
} else if (characteristic.getUuid().equals(DC_UUID.radarStatus)) {
radarStatus = characteristic.getValue();
}
}
@Override
protected void onCharacteristicWrite(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
super.onCharacteristicWrite(gatt, characteristic);
if (characteristic.getUuid().equals(DC_UUID.otaControl)) {
final byte[] otaControl = characteristic.getValue();
if (otaControl.length == 1) {
// OTA client initiates the update process
if (otaControl[0] == (byte) 0x00) {
// set OTA process flag
isOtaProcessing = true;
// check whether device is in OTA mode
if (isOtaMode.getValue()) {
// request MTU size
requestMtu(mtu);
// start update process,but ensure MTU size has been requested
handler.postDelayed(() -> uploadOta(), 2000);
} else {
// reconnect to establish OTA mode
isReconnectRequired = true;
// enforces device to reconnect
gatt.disconnect();
}
}
// OTA client finishes the update process
if (otaControl[0] == (byte) 0x03) {
if (isOtaProcessing) { // if device is in OTA mode and update process was successful
isOtaProcessing = false;
disconnect();
} else { // if device is in OTA mode, but update process was not established
// enforces device to reconnect
gatt.disconnect();
}
}
}
}
}
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
super.onCharacteristicChanged(gatt, characteristic);
}
};
public byte[] getDeviceId() {
return deviceId;
}
public void setDeviceId(final byte[] value) {
writeCharacteristic(deviceIdCharacteristic,
BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT,
value);
}
// Todo: implement other getters and setters
// Here I have to get the otaAppFilePath which I discovered in OtaFragment
public void uploadOta(final String otaAppFilePath) {
if (otaDataCharacteristic != null) {
otaDataCharacteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);
byte[] ebl = null;
try {
FileInputStream fileInputStream = new FileInputStream(otaAppFilePath);
int size = fileInputStream.available();
byte[] temp = new byte[size];
fileInputStream.read(temp);
fileInputStream.close();
ebl = temp;
} catch (Exception e) {
Logger.e(TAG, "Couldn't open file " + e);
}
otaAppFileStream = ebl;
pack = 0;
// start update process in another thread
Thread otaUploadThread = new Thread(() -> otaWriteDataReliable());
otaUploadThread.start();
}
}
private void writeCharacteristic(final BluetoothGattCharacteristic c,
final int writeType,
final byte[] value) {
if (c == null)
return;
c.setWriteType(writeType);
c.setValue(value);
writeCharacteristic(c); // will call the underlying API of BleManager
}
}

The code covers the basic use cases but I’m still not sure how to link the particular components with each other.

While reading about MVVM I noticed that there is always more than one possible solution/approach. I discovered the following questions:

  1. Is the ECountBleManager the right place to store the variables that I got by calling characteristics.getValue() and when yes, should I place the variables that I discover in OtaFragment in it too (that would mean, that I have to forward the values e.g. of mtu to the ECountBleManager)? Consider that I have to access the variables that I discover in OtaFragment and maybe other Fragments.
  2. Where do I store the variables from OtaFragment? In ECountVieModel or in ECountBleManager or do I create an OtaViewModel (but how could I access the ECountBleManager instance that I already created in ECountViewModel within the OtaViewModel?)
  3. How can I access mtu, priority and otaAppFile which were discovered in OtaFragment within the ECountBleManager?
  4. Do I have one ViewModel for every activity and fragment? But how to solve the problem with the ECountBleManager instance, see question 2?
  5. How does the ECountBleManager fit in the MVVM pattern? I would guess it is part of the Model?! But which part? Repository, Interactor, Controller, Mediator?

The code is not less so I’m sorry but you see I’m really try harding and I want to get better. I hope someone can help me with my questions and to improve my code. Thanks in advance!

How to&Answers: