@@ -34,11 +34,12 @@ import java.util.Collections
3434
3535/* *
3636 * Cloud uploader using Coroutines - No WorkManager dependency
37+ * Note: Nitro uses no-arg constructor via JNI, so context must be nullable
3738 */
3839@DoNotStrip
3940@Keep
4041class NitroCloudUploader (
41- private val appContext : Context
42+ private val injectedContext : Context ? = null
4243) : HybridNitroCloudUploaderSpec() {
4344
4445 companion object {
@@ -48,6 +49,10 @@ class NitroCloudUploader(
4849 private const val MAX_RETRIES = 3
4950 }
5051
52+ // ✅ Get context from injected param or ContentProvider (auto-initialized on app start)
53+ private val appContext: Context ?
54+ get() = injectedContext ? : ContextProvider .appContext
55+
5156 // ✅ Nullable with lazy initialization (Nitro JNI bridge bypasses normal initialization)
5257 private var _activeUploads : ConcurrentHashMap <String , UploadJob >? = null
5358 private var _uploadStates : ConcurrentHashMap <String , UploadStateData >? = null
@@ -130,9 +135,12 @@ class NitroCloudUploader(
130135 private val notificationManager: NotificationManager
131136 get() {
132137 if (_notificationManager == null ) {
133- synchronized(this ) {
134- if (_notificationManager == null ) {
135- _notificationManager = appContext.getSystemService(Context .NOTIFICATION_SERVICE ) as NotificationManager
138+ val ctx = appContext // Cache to avoid smart cast issues
139+ if (ctx != null ) {
140+ synchronized(this ) {
141+ if (_notificationManager == null ) {
142+ _notificationManager = ctx.getSystemService(Context .NOTIFICATION_SERVICE ) as NotificationManager
143+ }
136144 }
137145 }
138146 }
@@ -142,9 +150,12 @@ class NitroCloudUploader(
142150 private val connectivityManager: ConnectivityManager
143151 get() {
144152 if (_connectivityManager == null ) {
145- synchronized(this ) {
146- if (_connectivityManager == null ) {
147- _connectivityManager = appContext.getSystemService(Context .CONNECTIVITY_SERVICE ) as ConnectivityManager
153+ val ctx = appContext // Cache to avoid smart cast issues
154+ if (ctx != null ) {
155+ synchronized(this ) {
156+ if (_connectivityManager == null ) {
157+ _connectivityManager = ctx.getSystemService(Context .CONNECTIVITY_SERVICE ) as ConnectivityManager
158+ }
148159 }
149160 }
150161 }
@@ -218,6 +229,12 @@ class NitroCloudUploader(
218229
219230 private fun setupNotifications () {
220231 try {
232+ val ctx = appContext // Cache to avoid smart cast issues
233+ if (ctx == null ) {
234+ println (" ⚠️ appContext is null, skipping notification setup" )
235+ return
236+ }
237+
221238 if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .O ) {
222239 val channel = NotificationChannel (
223240 NOTIFICATION_CHANNEL_ID ,
@@ -236,6 +253,12 @@ class NitroCloudUploader(
236253
237254 private fun setupNetworkMonitoring () {
238255 try {
256+ val ctx = appContext // Cache to avoid smart cast issues
257+ if (ctx == null ) {
258+ println (" ⚠️ appContext is null, skipping network monitoring setup" )
259+ return
260+ }
261+
239262 val request = NetworkRequest .Builder ()
240263 .addCapability(NetworkCapabilities .NET_CAPABILITY_INTERNET )
241264 .build()
@@ -369,13 +392,24 @@ class NitroCloudUploader(
369392 }
370393
371394 // ✅ Stop foreground service on success
372- try {
373- UploadForegroundService .stopUploadService(appContext)
374- } catch (e: Exception ) {
375- println (" ⚠️ Failed to stop foreground service: ${e.message} " )
395+ val ctx = appContext // Cache to avoid smart cast issues
396+ if (ctx != null ) {
397+ try {
398+ UploadForegroundService .stopUploadService(ctx)
399+ } catch (e: Exception ) {
400+ println (" ⚠️ Failed to stop foreground service: ${e.message} " )
401+ }
376402 }
377403
378404 promise.resolve(result)
405+ } catch (e: CancellationException ) {
406+ // ✅ Cancellation is intentional, resolve with cancelled result
407+ println (" ⏸️ Upload cancelled: ${e.message} " )
408+ promise.resolve(UploadResult (
409+ uploadId = uploadId,
410+ success = false ,
411+ etags = emptyArray()
412+ ))
379413 } catch (e: Exception ) {
380414 println (" ❌ Upload failed: ${e.message} " )
381415
@@ -384,10 +418,13 @@ class NitroCloudUploader(
384418 }
385419
386420 // ✅ Stop foreground service on failure
387- try {
388- UploadForegroundService .stopUploadService(appContext)
389- } catch (serviceError: Exception ) {
390- println (" ⚠️ Failed to stop foreground service: ${serviceError.message} " )
421+ val ctx = appContext // Cache to avoid smart cast issues
422+ if (ctx != null ) {
423+ try {
424+ UploadForegroundService .stopUploadService(ctx)
425+ } catch (serviceError: Exception ) {
426+ println (" ⚠️ Failed to stop foreground service: ${serviceError.message} " )
427+ }
391428 }
392429
393430 promise.reject(e)
@@ -399,10 +436,13 @@ class NitroCloudUploader(
399436 activeUploads[uploadId] = UploadJob (uploadId, job)
400437
401438 // ✅ Start foreground service for background upload support
402- try {
403- UploadForegroundService .startUploadService(appContext, uploadId)
404- } catch (e: Exception ) {
405- println (" ⚠️ Failed to start foreground service: ${e.message} " )
439+ val ctx = appContext // Cache to avoid smart cast issues
440+ if (shouldNotify && ctx != null ) {
441+ try {
442+ UploadForegroundService .startUploadService(ctx, uploadId)
443+ } catch (e: Exception ) {
444+ println (" ⚠️ Failed to start foreground service: ${e.message} " )
445+ }
406446 }
407447
408448 emitEvent(
@@ -586,14 +626,19 @@ class NitroCloudUploader(
586626
587627 if (etag.isEmpty()) {
588628 println (" ⚠️ No ETag received for part ${part.partNumber} " )
629+ throw Exception (" No ETag in response for part ${part.partNumber} " )
589630 }
590631
591- // ✅ Update state
632+ // ✅ Update state - double-check not already counted
592633 synchronized(state) {
593- state.partETags[part.partNumber] = etag
594- state.completedChunks++
595- state.bytesUploaded + = part.size
596- state.failedChunks.remove(part.partNumber)
634+ if (! state.partETags.containsKey(part.partNumber)) {
635+ state.partETags[part.partNumber] = etag
636+ state.completedChunks++
637+ state.bytesUploaded + = part.size
638+ state.failedChunks.remove(part.partNumber)
639+ } else {
640+ println (" ⚠️ Part ${part.partNumber} was already marked as completed, skipping state update" )
641+ }
597642 }
598643
599644 val progress = state.bytesUploaded.toDouble() / state.totalBytes
@@ -630,15 +675,18 @@ class NitroCloudUploader(
630675 showNotification(uploadId, progressPercent, " Uploading... ${progressPercent} %" )
631676
632677 // ✅ Update foreground service notification
633- try {
634- UploadForegroundService .updateProgress(
635- appContext,
636- uploadId,
637- progressPercent,
638- " Uploading... ${progressPercent} %"
639- )
640- } catch (e: Exception ) {
641- println (" ⚠️ Failed to update foreground service: ${e.message} " )
678+ val ctx = appContext // Cache to avoid smart cast issues
679+ if (ctx != null ) {
680+ try {
681+ UploadForegroundService .updateProgress(
682+ ctx,
683+ uploadId,
684+ progressPercent,
685+ " Uploading... ${progressPercent} %"
686+ )
687+ } catch (e: Exception ) {
688+ println (" ⚠️ Failed to update foreground service: ${e.message} " )
689+ }
642690 }
643691 }
644692
@@ -769,10 +817,37 @@ class NitroCloudUploader(
769817 println (" 🛑 Cancelling upload: $uploadId " )
770818 val uploadJob = activeUploads[uploadId]
771819
820+ // ✅ Cancel job safely
772821 if (uploadJob != null ) {
773- uploadJob.job.cancel()
822+ try {
823+ if (uploadJob.job.isActive) {
824+ uploadJob.job.cancel() // Don't pass exception, just cancel
825+ println (" ✅ Upload job cancelled" )
826+ } else {
827+ println (" ⚠️ Upload job is not active, skipping cancellation" )
828+ }
829+ } catch (e: Exception ) {
830+ println (" ⚠️ Error cancelling job: ${e.message} " )
831+ }
832+ } else {
833+ println (" ⚠️ No active upload found for: $uploadId " )
774834 }
775835
836+ // ✅ Cleanup before emitting events (prevent accessing disposed resources)
837+ cleanup(uploadId)
838+ cancelNotification()
839+
840+ // ✅ Stop foreground service on cancel
841+ val ctx = appContext // Cache to avoid smart cast issues
842+ if (ctx != null ) {
843+ try {
844+ UploadForegroundService .stopUploadService(ctx)
845+ } catch (e: Exception ) {
846+ println (" ⚠️ Failed to stop foreground service: ${e.message} " )
847+ }
848+ }
849+
850+ // ✅ Emit event after cleanup
776851 emitEvent(
777852 UploadProgressEvent (
778853 type = " upload-cancelled" ,
@@ -785,16 +860,6 @@ class NitroCloudUploader(
785860 )
786861 )
787862
788- cleanup(uploadId)
789- cancelNotification()
790-
791- // ✅ Stop foreground service on cancel
792- try {
793- UploadForegroundService .stopUploadService(appContext)
794- } catch (e: Exception ) {
795- println (" ⚠️ Failed to stop foreground service: ${e.message} " )
796- }
797-
798863 promise.resolve(Unit )
799864 } catch (e: Exception ) {
800865 println (" ❌ Cancel error: ${e.message} " )
@@ -886,10 +951,17 @@ class NitroCloudUploader(
886951
887952 private fun showNotification (uploadId : String , progress : Int , message : String , isComplete : Boolean = false) {
888953 try {
954+ // ✅ Check if context is available and cache it
955+ val ctx = appContext
956+ if (ctx == null ) {
957+ println (" ⚠️ appContext is null, cannot show notification" )
958+ return
959+ }
960+
889961 // ✅ Check notification permission for Android 13+ (API 33+)
890962 if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .TIRAMISU ) {
891963 val hasPermission = ContextCompat .checkSelfPermission(
892- appContext ,
964+ ctx ,
893965 android.Manifest .permission.POST_NOTIFICATIONS
894966 ) == PackageManager .PERMISSION_GRANTED
895967
@@ -901,13 +973,13 @@ class NitroCloudUploader(
901973 }
902974
903975 // ✅ Create intent to open app when notification is tapped
904- val launchIntent = appContext .packageManager.getLaunchIntentForPackage(appContext .packageName)?.apply {
976+ val launchIntent = ctx .packageManager.getLaunchIntentForPackage(ctx .packageName)?.apply {
905977 flags = Intent .FLAG_ACTIVITY_NEW_TASK or Intent .FLAG_ACTIVITY_CLEAR_TOP
906978 }
907979
908980 val pendingIntent = if (launchIntent != null ) {
909981 PendingIntent .getActivity(
910- appContext ,
982+ ctx ,
911983 0 ,
912984 launchIntent,
913985 PendingIntent .FLAG_UPDATE_CURRENT or PendingIntent .FLAG_IMMUTABLE
@@ -916,7 +988,7 @@ class NitroCloudUploader(
916988 null
917989 }
918990
919- val notification = NotificationCompat .Builder (appContext , NOTIFICATION_CHANNEL_ID )
991+ val notification = NotificationCompat .Builder (ctx , NOTIFICATION_CHANNEL_ID )
920992 .setContentTitle(" Uploading File" )
921993 .setContentText(message)
922994 .setSmallIcon(android.R .drawable.stat_sys_upload)
0 commit comments