From 8ccdb9b75db7a171a5515a527ceddd41c912dce2 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Thu, 8 Oct 2026 14:17:10 +0200 Subject: [PATCH 1/4] inject dependencies for the remote client repository Signed-off-by: alperozturk96 --- .../java/com/nextcloud/client/di/AppModule.kt | 5 +++ .../repository/RemoteClientRepository.kt | 36 +++++++++++-------- .../android/ui/activity/BaseActivity.java | 6 +--- 3 files changed, 27 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/com/nextcloud/client/di/AppModule.kt b/app/src/main/java/com/nextcloud/client/di/AppModule.kt index c3f7ab13576c..7b5370afeae2 100644 --- a/app/src/main/java/com/nextcloud/client/di/AppModule.kt +++ b/app/src/main/java/com/nextcloud/client/di/AppModule.kt @@ -42,6 +42,8 @@ import com.nextcloud.client.notifications.AppNotificationManager import com.nextcloud.client.notifications.AppNotificationManagerImpl import com.nextcloud.client.preferences.AppPreferences import com.nextcloud.client.utils.Throttler +import com.nextcloud.repository.ClientRepository +import com.nextcloud.repository.RemoteClientRepository import com.nextcloud.utils.e2ee.E2EEActionResolver import com.nextcloud.utils.e2ee.E2EEKeyInspector import com.nextcloud.utils.thumbnail.FolderThumbnailGenerator @@ -114,6 +116,9 @@ internal class AppModule { fun filesRepository(accountManager: UserAccountManager, clientFactory: ClientFactory): FilesRepository = RemoteFilesRepository(FilesServiceApiImpl(accountManager, clientFactory)) + @Provides + fun clientRepository(repository: RemoteClientRepository): ClientRepository = repository + @Provides fun uploadsStorageManager(currentAccountProvider: CurrentAccountProvider, context: Context): UploadsStorageManager = UploadsStorageManager(currentAccountProvider, context.contentResolver) diff --git a/app/src/main/java/com/nextcloud/repository/RemoteClientRepository.kt b/app/src/main/java/com/nextcloud/repository/RemoteClientRepository.kt index 5bdfa82dae9a..7b4c197e68c6 100644 --- a/app/src/main/java/com/nextcloud/repository/RemoteClientRepository.kt +++ b/app/src/main/java/com/nextcloud/repository/RemoteClientRepository.kt @@ -8,40 +8,46 @@ package com.nextcloud.repository import android.content.Context -import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.lifecycleScope -import com.nextcloud.client.account.User +import com.nextcloud.client.account.UserAccountManager +import com.nextcloud.client.di.ApplicationScope import com.nextcloud.common.NextcloudClient import com.owncloud.android.lib.common.OwnCloudClient import com.owncloud.android.lib.common.OwnCloudClientManagerFactory import com.owncloud.android.lib.common.utils.Log_OC +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import javax.inject.Inject @Suppress("TooGenericExceptionCaught", "DEPRECATION") -class RemoteClientRepository(private val user: User, private val context: Context, lifecycleOwner: LifecycleOwner) : - ClientRepository { - private val tag = "ClientRepository" +class RemoteClientRepository @Inject constructor( + private val context: Context, + private val accountManager: UserAccountManager, + @param:ApplicationScope private val scope: CoroutineScope +) : ClientRepository { + companion object { + private const val TAG = "ClientRepository" + } + private val clientFactory = OwnCloudClientManagerFactory.getDefaultSingleton() - private val scope = lifecycleOwner.lifecycleScope override fun getNextcloudClient(onComplete: (NextcloudClient) -> Unit) { scope.launch(Dispatchers.IO) { try { - val client = clientFactory.getNextcloudClientFor(user.toOwnCloudAccount(), context) + val client = clientFactory.getNextcloudClientFor(accountManager.user.toOwnCloudAccount(), context) onComplete(client) } catch (e: Exception) { - Log_OC.d(tag, "Exception caught getNextcloudClient(): $e") + Log_OC.d(TAG, "Exception caught getNextcloudClient(): $e") } } } override suspend fun getNextcloudClient(): NextcloudClient? = withContext(Dispatchers.IO) { try { - clientFactory.getNextcloudClientFor(user.toOwnCloudAccount(), context) + clientFactory.getNextcloudClientFor(accountManager.user.toOwnCloudAccount(), context) } catch (e: Exception) { - Log_OC.d(tag, "Exception caught getNextcloudClient(): $e") + Log_OC.d(TAG, "Exception caught getNextcloudClient(): $e") null } } @@ -49,19 +55,19 @@ class RemoteClientRepository(private val user: User, private val context: Contex override fun getOwncloudClient(onComplete: (OwnCloudClient) -> Unit) { scope.launch(Dispatchers.IO) { try { - val client = clientFactory.getClientFor(user.toOwnCloudAccount(), context) + val client = clientFactory.getClientFor(accountManager.user.toOwnCloudAccount(), context) onComplete(client) } catch (e: Exception) { - Log_OC.d(tag, "Exception caught getOwncloudClient(): $e") + Log_OC.d(TAG, "Exception caught getOwncloudClient(): $e") } } } override suspend fun getOwncloudClient(): OwnCloudClient? = withContext(Dispatchers.IO) { try { - clientFactory.getClientFor(user.toOwnCloudAccount(), context) + clientFactory.getClientFor(accountManager.user.toOwnCloudAccount(), context) } catch (e: Exception) { - Log_OC.d(tag, "Exception caught getOwncloudClient(): $e") + Log_OC.d(TAG, "Exception caught getOwncloudClient(): $e") null } } diff --git a/app/src/main/java/com/owncloud/android/ui/activity/BaseActivity.java b/app/src/main/java/com/owncloud/android/ui/activity/BaseActivity.java index caa83471149a..e73e4fbcfc1e 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/BaseActivity.java +++ b/app/src/main/java/com/owncloud/android/ui/activity/BaseActivity.java @@ -20,7 +20,6 @@ import com.nextcloud.client.preferences.AppPreferences; import com.nextcloud.client.preferences.DarkMode; import com.nextcloud.repository.ClientRepository; -import com.nextcloud.repository.RemoteClientRepository; import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.lib.common.utils.Log_OC; @@ -53,6 +52,7 @@ public abstract class BaseActivity extends AppCompatActivity implements Injectab @Inject UserAccountManager accountManager; @Inject AppPreferences preferences; @Inject FileDataStorageManager fileDataStorageManager; + @Inject ClientRepository clientRepository; private final AppPreferences.Listener onPreferencesChanged = new AppPreferences.Listener() { @Override @@ -65,8 +65,6 @@ public UserAccountManager getUserAccountManager() { return accountManager; } - private ClientRepository clientRepository; - @Override protected void onCreate(@Nullable Bundle savedInstanceState) { AppCompatActivityExtensionsKt.applyEdgeToEdgeWithSystemBarPadding(this); @@ -77,8 +75,6 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { if (enableAccountHandling) { mixinRegistry.onCreate(savedInstanceState); } - - clientRepository = new RemoteClientRepository(accountManager.getUser(), this, this); } @Override From 824dedfbb7c426aaade89b747ccc4c72e9c4e0fb Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Thu, 8 Oct 2026 14:19:44 +0200 Subject: [PATCH 2/4] use ClientRepository in AlbumItemsFragment for future reference Signed-off-by: alperozturk96 --- .../ui/fragment/albums/AlbumItemsFragment.kt | 42 ++++--------------- 1 file changed, 8 insertions(+), 34 deletions(-) diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/albums/AlbumItemsFragment.kt b/app/src/main/java/com/owncloud/android/ui/fragment/albums/AlbumItemsFragment.kt index d620e785abcb..fd790a616fd0 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/albums/AlbumItemsFragment.kt +++ b/app/src/main/java/com/owncloud/android/ui/fragment/albums/AlbumItemsFragment.kt @@ -43,10 +43,9 @@ import com.nextcloud.client.account.User import com.nextcloud.client.account.UserAccountManager import com.nextcloud.client.di.Injectable import com.nextcloud.client.jobs.upload.FileUploadWorker -import com.nextcloud.client.network.ClientFactory -import com.nextcloud.client.network.ClientFactory.CreationException import com.nextcloud.client.utils.IntentUtil import com.nextcloud.client.utils.Throttler +import com.nextcloud.repository.ClientRepository import com.nextcloud.ui.albumItemActions.AlbumItemActionsBottomSheet import com.nextcloud.ui.fileactions.FileActionsBottomSheet import com.nextcloud.utils.SnackbarUtil @@ -122,7 +121,7 @@ class AlbumItemsFragment : lateinit var accountManager: UserAccountManager @Inject - lateinit var clientFactory: ClientFactory + lateinit var clientRepository: ClientRepository @Inject lateinit var throttler: Throttler @@ -135,7 +134,6 @@ class AlbumItemsFragment : private var adapter: GalleryAdapter? = null private var addMediaFab: FloatingActionButton? = null - private var client: OwnCloudClient? = null private var optionalUser: Optional? = null private var containerActivity: FileFragment.ContainerActivity? = null private var selectionMode: AlbumItemsMultiChoiceModeListener? = null @@ -286,24 +284,7 @@ class AlbumItemsFragment : binding.listFragmentLayout.addView(addMediaFab, layoutParams) } - private fun initializeClient() { - if (client != null) { - return - } - - val user = optionalUser?.takeIf { it.isPresent }?.get() ?: return - - client = try { - clientFactory.create(user) - } catch (e: CreationException) { - Log_OC.e(TAG, "Error initializing client", e) - null - } - } - private fun initializeAdapter() { - initializeClient() - if (adapter == null) { adapter = GalleryAdapter( requireContext(), @@ -365,7 +346,7 @@ class AlbumItemsFragment : showLoadingMessageWhenReachable() lifecycleScope.launch(Dispatchers.IO) { - val client = client ?: run { + val client = clientRepository.getOwncloudClient() ?: run { withContext(Dispatchers.Main) { onAlbumItemsFailed(null) } return@launch } @@ -434,7 +415,7 @@ class AlbumItemsFragment : fun refreshAlbumMetaData() { lifecycleScope.launch(Dispatchers.IO) { val albumsRemoteOperation = ReadAlbumsRemoteOperation(albumName) - val result = client?.let { albumsRemoteOperation.execute(it) } + val result = clientRepository.getOwncloudClient()?.let { albumsRemoteOperation.execute(it) } withContext(Dispatchers.Main) { photoAlbumEntry = result @@ -666,18 +647,16 @@ class AlbumItemsFragment : @Subscribe(threadMode = ThreadMode.BACKGROUND) fun onMessageEvent(event: FavoriteEvent) { - try { - val client = clientFactory.create(accountManager.user) + lifecycleScope.launch(Dispatchers.IO) { + val client = clientRepository.getOwncloudClient() ?: return@launch val toggleFavoriteOperation = ToggleAlbumFavoriteRemoteOperation(event.shouldFavorite, event.remotePath) if (!toggleFavoriteOperation.execute(client).isSuccess) { - return + return@launch } Handler(Looper.getMainLooper()).post { selectionMode?.exitSelectionMode() } adapter?.markAsFavorite(event.remotePath, event.shouldFavorite) - } catch (e: CreationException) { - Log_OC.e(TAG, "Error processing event", e) } } //endregion @@ -727,12 +706,7 @@ class AlbumItemsFragment : lifecycleScope.launch(Dispatchers.IO) { withContext(Dispatchers.Main) { showLoadingDialog() } - val failedFiles = try { - removeFromAlbum(clientFactory.create(accountManager.user), files) - } catch (e: CreationException) { - Log_OC.e(TAG, "Error removing album files", e) - emptyList() - } + val failedFiles = clientRepository.getOwncloudClient()?.let { removeFromAlbum(it, files) }.orEmpty() Log_OC.d(TAG, "Files that could not be removed: ${failedFiles.size}") From 49dcfdb2702eaf1b45f7f6a358aa7d5747e0f32d Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Thu, 8 Oct 2026 14:32:42 +0200 Subject: [PATCH 3/4] update AGENTS.md fur further usages Signed-off-by: alperozturk96 --- AGENTS.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index 79f9715c3bdf..1323aa8613c2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -96,6 +96,16 @@ Use `StateFlow` and `MutableStateFlow` in ViewModels for state management. Colle Uses Dagger 2 for major Android components (`Activity`, `Fragment`, `Service`, `BroadcastReceiver`, `ContentProvider`). Manual constructor injection for other components. +### Network Clients + +Get `OwnCloudClient` and `NextcloudClient` instances from `ClientRepository` (`com.nextcloud.repository`), never from `ClientFactory`. +`ClientFactory` is deprecated and being phased out; do not add new usages of it, and replace it with `ClientRepository` when touching code that still uses it. + +- Inject `ClientRepository` with Dagger (`@Inject lateinit var clientRepository: ClientRepository`); it is bound to `RemoteClientRepository`, which resolves the current user via `UserAccountManager` on every call. +- Activities extending `BaseActivity` already expose it via `getClientRepository()`. +- Prefer the `suspend` functions `getOwncloudClient()` / `getNextcloudClient()` from a lifecycle-aware scope (`lifecycleScope`, `viewModelScope`); they switch to `Dispatchers.IO` and return `null` on failure, so handle `null` instead of catching exceptions. +- Do not construct `RemoteClientRepository` manually. + ### Extension Functions The `com.nextcloud.utils.extensions` package contains helper extensions organized by type (e.g., `FileExtensions.kt`, `StringExtensions.kt`, `ViewExtensions.kt`). Create focused extension files rather than putting multiple types in one file. From a880fd267e0e7d6f768624c160f2ca56fd5a6c63 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Thu, 8 Oct 2026 14:34:21 +0200 Subject: [PATCH 4/4] deprecate ClientFactory Signed-off-by: alperozturk96 --- .../java/com/nextcloud/client/network/ClientFactory.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/src/main/java/com/nextcloud/client/network/ClientFactory.java b/app/src/main/java/com/nextcloud/client/network/ClientFactory.java index a0780bdbeb08..c3f18f4bde71 100644 --- a/app/src/main/java/com/nextcloud/client/network/ClientFactory.java +++ b/app/src/main/java/com/nextcloud/client/network/ClientFactory.java @@ -21,6 +21,11 @@ import java.io.IOException; +/** + * @deprecated Inject {@link com.nextcloud.repository.ClientRepository} and use its suspend + * {@code getOwncloudClient()} / {@code getNextcloudClient()} functions instead. + */ +@Deprecated public interface ClientFactory { /**