android10 / Android-CleanArchitecture-Kotlin

This is a movies sample app in Kotlin, which is part of a serie of blog posts I have written about architecting android application using different approaches.
https://fernandocejas.com/2018/05/07/architecting-android-reloaded/
4.63k stars 921 forks source link

Question: How to use Android Framework features that rely on Activities, BroadcastReceivers, etc #104

Open balazsgerlei opened 3 years ago

balazsgerlei commented 3 years ago

I proposed this question originally on Twitter to @android10 and he was right that an issue is better. I implemented similar architectures in apps but frequently wondered what is the best way to integrate features provided by the Android Framework that rely heavily on Activities, Fragments, BroadcastReceivers, etc. (I kind of just hacked these into it if I had to TBH)

I would like to ask this question generally about any such features, but in order to talk about this easier, I will show an example: DownloadManager

It's nice functionality that is provided by the system and while of course downloading something can be done via Retrofit or some other implementation as well, DownloadManager handles the UI feedback (via Notifications), errors and retries, reboots, etc. so it has different pros and cons that might be more fitting to an app. And think about the (storage) permission requirements, they are compeletely different if you use your own logic vs DownloadManager.

Instinct may suggest making this a Repository as well, but DownloadManager relies on Broadcasts to notify the app about the download result for example, so it cannot really be put in a Repository. An example BroadcastReceiver to be notified about the result and get the path for the downloaded file:

private class DownloadCompletedBroadcastReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        final Bundle extras = intent.getExtras();
        if (extras != null) {
            final long downloadId = extras.getLong(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
            final DownloadManager.Query query = new DownloadManager.Query().setFilterById(downloadId);
            try (Cursor c = downloadManager.query(query)) {
                if (c.moveToFirst()) {
                    final int status = c.getInt(c.getColumnIndex(DownloadManager.COLUMN_STATUS));
                    if (status == DownloadManager.STATUS_SUCCESSFUL) {
                        final String uriString = c.getString(c.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI));
                        final String filePath = Uri.parse(uriString).getPath(); // This is the path of the downloaded File
                    }
                }
            } catch (Exception ignored) {}
        }
    }

};

So how would be the best way to integrate such features with this architecture?

android10 commented 3 years ago

hey @balazsgerlei sorry for the late reply. After thinking a bit about it, I do not think there is a clean solution without breaking the core of this architecture.

This architecture exists for decoupling framework components in order to favor modularization and testing.

I would wait for someone else to reply but my take would be to just not use DownloadManager because from my perspective it is already coupling the framework by forcing the usage of a BroadcastReceiver.

You can have the same result by just using Retrofit for downloading a file, thus sticking to the architecture.

balazsgerlei commented 3 years ago

No problem and it's not an urgent question or anything, but I would be also very interested in other's take. As I mentioned I did implemented architectures inspired by this one and in some occasions I had to also integrate Android Framework features like DownloadManager - again this was only an example as I do remember needing it at one point, but there are certainly other similar APIs on Android.

As I also mentioned, what I did in that case was kind of "hacked it into" the architecture, basically circumventing the architecture by registering the BroadcastReceiver in the single Activity of the app and forwarding the result to the Fragment that is displayed. I didn't need too much logic anyway, as I just cancelled the indeterminate ProgressBar and displayed a SnackBar about the result (that the file is downloaded to a particular path). Even though I had Retrofit in that project and I'm well aware that the download could be implemented with that (and in fact it was at first), but the features that DownloadManager provides were ultimately more important (handling retries, connectivity issues, system restarts) - even though most of these (or maybe all) could have also been implemented with custom logic, it would certainly take much-much more effort and produce considerably more code that need to be maintained so I decided to use DownloadManager though I would have been much happier if I could have integrated it better in the architecture. And ever since (it was multiple years ago) I was wondering how it could have been done but I only thought about asking about it now.