Non-existent Notification of an Issue while Downloading Content (NNDC)

ECn Categories > Non-Existent Result Notification (NRN) > Non-existent Notification of Problem when Performing an Action (NNPPA) > Non-existent Notification of an Issue while Downloading Content (NNDC)

The app does not show anything indicating that the required content is being downloaded. It seems that nothing happened.

Amount of Issues App List
7 (A4) Galaxy Zoo, (A5) Fanfiction reader, (A10) OsmAnd, (A12) XOWA, (A20) MAPS.ME

Examples

Stepik

Category: Education
v 1.42
Scenario: To download a course with internet connection
This video shows an example of a nonexistent notification of progress while downloading content. At 15th second the user clicks in the download button of a course section. Then the app asks for the desired video quality of the videos, when the users make a choice and click in the "OK" button at 18th second the app returns to the course section list and a "stop" button is displayed to the user. Nevertheless, as it can be seen in the video there is no way to know if the download is being performed.

The following code snippets show the classes and files that are involved in the generation of the previuos issue.

In order to see how this error is generated, we need to review all the flow event. First, as it can be seen in the following snippet and knowing that it is in the adapter class, the following “onClickListener” is called everytime a new download is started. As it can be seen, this method calls the “onClickLoad” method.

app/src/main/java/org/stepic/droid/ui/adapters/SectionAdapter.java

    loadButton.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
372.        onClickLoad(getAdapterPosition());
        }
    });

This method after some validations about permissions, make a validation to know if the click is starting a download, stoping it or deleting the content. For this specific case the content is being downloaded, so as is shown at line 231, after the dialog that allows the user to select the video quality for the download is shown and a option is selected the flow event lands in the “setOnLoadPositionListener”.

app/src/main/java/org/stepic/droid/ui/adapters/SectionAdapter.java

public void onClickLoad(int adapterPosition) {
    ...
    } else {
        if (sharedPreferenceHelper.isNeedToShowVideoQualityExplanation()) {
            VideoQualityDetailedDialog dialogFragment = VideoQualityDetailedDialog.Companion.newInstan(adapterPosition);
231.        dialogFragment.setOnLoadPositionListener(this);
            if (!dialogFragment.isAdded()) {
                FragmentTransaction ft = activity.getSupportFragmentManager().beginTransaction();
                ft.add(dialogFragment, null);
                ft.commitAllowingStateLoss();
            }
        } else {
            loadSection(adapterPosition);
        }
    }

That listener in the dialog is assigned to “onNeedLoadPosition”, and as is shown that method calls “loadSection” method.

app/src/main/java/org/stepic/droid/ui/adapters/SectionAdapter.java

    public void onNeedLoadPosition(int adapterPosition) {
277.    loadSection(adapterPosition);
    }

Therefore when reviewing the code of the “loadSection”, after a validation of the parameter the “checkOnLoading” method from “downloadingInteractionPresenter” is called

app/src/main/java/org/stepic/droid/ui/adapters/SectionAdapter.java

    private void loadSection(int adapterPosition) {
        int sectionPosition = adapterPosition - PRE_SECTION_LIST_DELTA;
        if (sectionPosition >= 0 && sectionPosition < sections.size()) {
248.        downloadingInteractionPresenter.checkOnLoading(adapterPosition);
        }
    }

Second, when the event flow gets into “DownloadinInteractionPresenter”, the method checkOnLoading calls the “checkOnLoadingExclusive”method after a validation.

app/src/main/java/org/stepic/droid/core/presenters/DownloadingInteractionPresenter.kt

    fun checkOnLoading(position: Int) {
        if (isHandling.compareAndSet(false, true)) {
            threadPoolExecutor.execute {
                try {
27.                 checkOnLoadingExclusive(position)
                } finally {
                    isHandling.set(false)
                }
            }
        }
    }

However, this method based on the current state of the connection calls a different method, but as it can be seen in the video the issue appears with wifi connection, therefore at line 40 the case for wifi calls the “onLoadingAccepted” method.

app/src/main/java/org/stepic/droid/core/presenters/DownloadingInteractionPresenter.kt

    private fun checkOnLoadingExclusive(position: Int) {
        val networkType = networkTypeDeterminer.determineNetworkType()
        with(mainHandler) {
            when (networkType) {
40.             NetworkType.wifi -> post { view?.onLoadingAccepted(position) }
                NetworkType.none -> post { view?.onShowInternetIsNotAvailableRetry(position) }
                NetworkType.onlyMobile -> {
                    if (userPreferences.isNetworkMobileAllowed) {
                        post { view?.onLoadingAccepted(position) }
                    } else {
                        post { view?.onShowPreferenceSuggestion() }
                    }
                }
            }
        }
    }

Nevertheless, the “onLoadingAccepted” method only calls the “loadAfterDetermineNetworkState” method.

app/src/main/java/org/stepic/droid/ui/fragments/SectionsFragment.java

    public void onLoadingAccepted(int position) {
878.    adapter.loadAfterDetermineNetworkState(position);
    }

Third, the event flow arrives again into the “SectionAdapter” class, in this case the “loadAfterDetermineNetworkState” method save some statistics and changes some state for the section in order to know that will be downloaded. After that at line 264 inside a new thread the download request is made using the “downloadSection” method from the “SectionDownloader”.

app/src/main/java/org/stepic/droid/ui/adapters/SectionAdapter.java

    public void loadAfterDetermineNetworkState(int adapterPosition) {
        int sectionPosition = adapterPosition - PRE_SECTION_LIST_DELTA;
        if (sectionPosition >= 0 && sectionPosition < sections.size()) {
            final Section section = sections.get(sectionPosition);
            analytic.reportEvent(Analytic.Interaction.CLICK_CACHE_SECTION, section.getId() + "");
            section.setCached(false);
            section.setLoading(true);
            downloadingPresenter.onStateChanged(section.getId(), section.isLoading());
            threadPoolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    databaseFacade.updateOnlyCachedLoadingSection(section);
264.                sectionDownloader.downloadSection(section.getId());
                }
            });
            notifyItemChanged(adapterPosition);
        }
    }

However, the “downloadSection” method creates a new task to a “threadPool” and executes the “addSection” method from the “DownloadManager” class

app/src/main/java/org/stepic/droid/storage/SectionDownloaderImpl.kt

    override fun downloadSection(sectionId: Long) {
        threadPoolExecutor.execute {
            val section = databaseFacade.getSectionById(sectionId)
            cancelSniffer.removeSectionIdCancel(sectionId)
19.         downloadManager.addSection(section)
        }
    }

Fourth, when the “addSection” method is called, a new intent is create for LoadService class at the line 27.

app/src/main/java/org/stepic/droid/storage/DownloadManagerImpl.java

    public void addSection(Section section) {
27.     Intent loadIntent = new Intent(App.Companion.getAppContext(), LoadService.class);

        loadIntent.putExtra(AppConstants.KEY_LOAD_TYPE, LoadService.LoadTypeKey.Section);
        loadIntent.putExtra(AppConstants.KEY_SECTION_BUNDLE, (Serializable) section);

        App.Companion.getAppContext().startService(loadIntent);
    }

As the class represent a service the method “onHandleIntent” is called when an intent is started to the class, and as it can be seen at line 98 knowing that the previous method send the extra that represents the type, the method called when a session is being tried to download.

app/src/main/java/org/stepic/droid/services/LoadService.java

    protected void onHandleIntent(Intent intent) {
        LoadTypeKey type = (LoadTypeKey) intent.getSerializableExtra(AppConstants.KEY_LOAD_TYPE);
        try {
            switch (type) {
                case Section:
                    Section section = (Section) intent.getSerializableExtra(AppConstants.KEY_SECTION_BUNDLE);
                    addSection(section);
                    break;

Finally, in “addSection” method of the “LoadService” the request is made, the snippet shows all the process made by the method. Between lines 318 and 328 the method make an iteration to get all the videos that belong to the section, and it can be seen that there is no progress storing to let the user know the download is in progress

app/src/main/java/org/stepic/droid/services/LoadService.java

    private void addSection(Section sectionOut) {
        //if user click to removeSection, then section already in database.
        Section section = databaseFacade.getSectionById(sectionOut.getId());//make copy of section.
        if (section != null) {
            try {
                boolean responseIsSuccess = true;
                final List<Unit> units = new ArrayList<>();
                long[] unitIds = section.getUnits();
                if (unitIds == null) {
                    responseIsSuccess = false;
                }
                int pointer = 0;
                while (responseIsSuccess && pointer < unitIds.length) {
                    int lastExclusive = Math.min(unitIds.length, pointer + AppConstants.DEFAULT_NUMBER_IDS_IN_QUERY);
                    long[] subArrayForLoading = Arrays.copyOfRange(unitIds, pointer, lastExclusive);
                    Response<UnitMetaResponse> unitResponse = api.getUnits(subArrayForLoading).execute();
                    if (!unitResponse.isSuccessful()) {
                        responseIsSuccess = false;
                    } else {
                        units.addAll(unitResponse.body().getUnits());
                        pointer = lastExclusive;
                    }
                }