อัปโหลดไฟล์ด้วย Cloud Storage บน Android

Cloud Storage for Firebase ช่วยให้คุณอัปโหลดไฟล์ไปยังที่เก็บข้อมูล Cloud Storage ที่ Firebase มีให้และจัดการได้อย่างรวดเร็วและง่ายดาย

อัปโหลดไฟล์

หากต้องการอัปโหลดไฟล์ไปยัง Cloud Storage ก่อนอื่นคุณต้องสร้างการอ้างอิงถึงเส้นทางแบบเต็มของไฟล์ รวมถึงชื่อไฟล์

Kotlin+KTX

// Create a storage reference from our app
val storageRef = storage.reference

// Create a reference to "mountains.jpg"
val mountainsRef = storageRef.child("mountains.jpg")

// Create a reference to 'images/mountains.jpg'
val mountainImagesRef = storageRef.child("images/mountains.jpg")

// While the file names are the same, the references point to different files
mountainsRef.name == mountainImagesRef.name // true
mountainsRef.path == mountainImagesRef.path // false

Java

// Create a storage reference from our app
StorageReference storageRef = storage.getReference();

// Create a reference to "mountains.jpg"
StorageReference mountainsRef = storageRef.child("mountains.jpg");

// Create a reference to 'images/mountains.jpg'
StorageReference mountainImagesRef = storageRef.child("images/mountains.jpg");

// While the file names are the same, the references point to different files
mountainsRef.getName().equals(mountainImagesRef.getName());    // true
mountainsRef.getPath().equals(mountainImagesRef.getPath());    // false

เมื่อสร้างการอ้างอิงที่เหมาะสมแล้ว ให้เรียกใช้เมธอด putBytes(), putFile() หรือ putStream() เพื่ออัปโหลดไฟล์ไปที่ Cloud Storage

คุณไม่สามารถอัปโหลดข้อมูลที่มีการอ้างอิงถึงรูทของที่เก็บข้อมูล Cloud Storage การอ้างอิงต้องชี้ไปยัง URL ย่อย

อัปโหลดจากข้อมูลในหน่วยความจำ

วิธี putBytes() เป็นวิธีที่ง่ายที่สุดในการอัปโหลดไฟล์ไปยัง Cloud Storage putBytes() จะรับ byte[] และแสดงผล UploadTask ที่คุณสามารถใช้เพื่อจัดการและตรวจสอบสถานะการอัปโหลด

Kotlin+KTX

// Get the data from an ImageView as bytes
imageView.isDrawingCacheEnabled = true
imageView.buildDrawingCache()
val bitmap = (imageView.drawable as BitmapDrawable).bitmap
val baos = ByteArrayOutputStream()
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos)
val data = baos.toByteArray()

var uploadTask = mountainsRef.putBytes(data)
uploadTask.addOnFailureListener {
    // Handle unsuccessful uploads
}.addOnSuccessListener { taskSnapshot ->
    // taskSnapshot.metadata contains file metadata such as size, content-type, etc.
    // ...
}

Java

// Get the data from an ImageView as bytes
imageView.setDrawingCacheEnabled(true);
imageView.buildDrawingCache();
Bitmap bitmap = ((BitmapDrawable) imageView.getDrawable()).getBitmap();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);
byte[] data = baos.toByteArray();

UploadTask uploadTask = mountainsRef.putBytes(data);
uploadTask.addOnFailureListener(new OnFailureListener() {
    @Override
    public void onFailure(@NonNull Exception exception) {
        // Handle unsuccessful uploads
    }
}).addOnSuccessListener(new OnSuccessListener<UploadTask.TaskSnapshot>() {
    @Override
    public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) {
        // taskSnapshot.getMetadata() contains file metadata such as size, content-type, etc.
        // ...
    }
});

เนื่องจาก putBytes() ยอมรับ byte[] แอปของคุณจึงต้องเก็บเนื้อหาทั้งหมดของไฟล์ไว้ในหน่วยความจำพร้อมกัน ลองใช้ putStream() หรือ putFile() เพื่อใช้หน่วยความจําน้อยลง

อัปโหลดจากสตรีม

วิธี putStream() เป็นวิธีที่หลากหลายที่สุดในการอัปโหลดไฟล์ไปยัง Cloud Storage putStream() จะรับ InputStream และแสดงผล UploadTask ที่คุณใช้จัดการและตรวจสอบสถานะการอัปโหลดได้

Kotlin+KTX

val stream = FileInputStream(File("path/to/images/rivers.jpg"))

uploadTask = mountainsRef.putStream(stream)
uploadTask.addOnFailureListener {
    // Handle unsuccessful uploads
}.addOnSuccessListener { taskSnapshot ->
    // taskSnapshot.metadata contains file metadata such as size, content-type, etc.
    // ...
}

Java

InputStream stream = new FileInputStream(new File("path/to/images/rivers.jpg"));

uploadTask = mountainsRef.putStream(stream);
uploadTask.addOnFailureListener(new OnFailureListener() {
    @Override
    public void onFailure(@NonNull Exception exception) {
        // Handle unsuccessful uploads
    }
}).addOnSuccessListener(new OnSuccessListener<UploadTask.TaskSnapshot>() {
    @Override
    public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) {
        // taskSnapshot.getMetadata() contains file metadata such as size, content-type, etc.
        // ...
    }
});

อัปโหลดจากไฟล์ในเครื่อง

คุณสามารถอัปโหลดไฟล์ในเครื่อง เช่น รูปภาพและวิดีโอจากกล้อง ด้วยวิธี putFile() putFile() จะรับ File และแสดงผล UploadTask ซึ่งคุณใช้เพื่อจัดการและตรวจสอบสถานะการอัปโหลดได้

Kotlin+KTX

var file = Uri.fromFile(File("path/to/images/rivers.jpg"))
val riversRef = storageRef.child("images/${file.lastPathSegment}")
uploadTask = riversRef.putFile(file)

// Register observers to listen for when the download is done or if it fails
uploadTask.addOnFailureListener {
    // Handle unsuccessful uploads
}.addOnSuccessListener { taskSnapshot ->
    // taskSnapshot.metadata contains file metadata such as size, content-type, etc.
    // ...
}

Java

Uri file = Uri.fromFile(new File("path/to/images/rivers.jpg"));
StorageReference riversRef = storageRef.child("images/"+file.getLastPathSegment());
uploadTask = riversRef.putFile(file);

// Register observers to listen for when the download is done or if it fails
uploadTask.addOnFailureListener(new OnFailureListener() {
    @Override
    public void onFailure(@NonNull Exception exception) {
        // Handle unsuccessful uploads
    }
}).addOnSuccessListener(new OnSuccessListener<UploadTask.TaskSnapshot>() {
    @Override
    public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) {
        // taskSnapshot.getMetadata() contains file metadata such as size, content-type, etc.
        // ...
    }
});

รับ URL ดาวน์โหลด

หลังจากอัปโหลดไฟล์แล้ว คุณสามารถรับ URL เพื่อดาวน์โหลดไฟล์ได้โดยเรียกใช้เมธอด getDownloadUrl() ใน StorageReference ดังนี้

Kotlin+KTX

val ref = storageRef.child("images/mountains.jpg")
uploadTask = ref.putFile(file)

val urlTask = uploadTask.continueWithTask { task ->
    if (!task.isSuccessful) {
        task.exception?.let {
            throw it
        }
    }
    ref.downloadUrl
}.addOnCompleteListener { task ->
    if (task.isSuccessful) {
        val downloadUri = task.result
    } else {
        // Handle failures
        // ...
    }
}

Java

final StorageReference ref = storageRef.child("images/mountains.jpg");
uploadTask = ref.putFile(file);

Task<Uri> urlTask = uploadTask.continueWithTask(new Continuation<UploadTask.TaskSnapshot, Task<Uri>>() {
    @Override
    public Task<Uri> then(@NonNull Task<UploadTask.TaskSnapshot> task) throws Exception {
        if (!task.isSuccessful()) {
            throw task.getException();
        }

        // Continue with the task to get the download URL
        return ref.getDownloadUrl();
    }
}).addOnCompleteListener(new OnCompleteListener<Uri>() {
    @Override
    public void onComplete(@NonNull Task<Uri> task) {
        if (task.isSuccessful()) {
            Uri downloadUri = task.getResult();
        } else {
            // Handle failures
            // ...
        }
    }
});

เพิ่มข้อมูลเมตาของไฟล์

นอกจากนี้ คุณยังใส่ข้อมูลเมตาเมื่ออัปโหลดไฟล์ได้ด้วย ข้อมูลเมตานี้มีพร็อพเพอร์ตี้ข้อมูลเมตาของไฟล์ทั่วไป เช่น name, size และ contentType (โดยทั่วไปเรียกว่าประเภท MIME) เมธอด putFile() จะอนุมานประเภท MIME จากส่วนขยาย File โดยอัตโนมัติ แต่คุณสามารถลบล้างประเภทที่ตรวจพบโดยอัตโนมัติได้โดยระบุ contentType ในข้อมูลเมตา หากคุณไม่ได้ระบุ contentType และ Cloud Storage ไม่สามารถอนุมานค่าเริ่มต้นจากนามสกุลไฟล์ Cloud Storage จะใช้ application/octet-stream ดูข้อมูลเพิ่มเติมเกี่ยวกับข้อมูลเมตาของไฟล์ได้ที่ส่วนใช้ข้อมูลเมตาของไฟล์

Kotlin+KTX

// Create file metadata including the content type
var metadata = storageMetadata {
    contentType = "image/jpg"
}

// Upload the file and metadata
uploadTask = storageRef.child("images/mountains.jpg").putFile(file, metadata)

Java

// Create file metadata including the content type
StorageMetadata metadata = new StorageMetadata.Builder()
        .setContentType("image/jpg")
        .build();

// Upload the file and metadata
uploadTask = storageRef.child("images/mountains.jpg").putFile(file, metadata);

จัดการการอัปโหลด

นอกจากการเริ่มอัปโหลดแล้ว คุณยังหยุดชั่วคราว เล่นต่อ และยกเลิกการอัปโหลดได้โดยใช้ปุ่ม pause(), resume() และ cancel() เหตุการณ์ "หยุดชั่วคราว" และ "เล่นต่อ" จะทำให้เกิดการเปลี่ยนแปลงสถานะ pause และ progress ตามลำดับ การยกเลิกการอัปโหลดจะทำให้การอัปโหลดไม่สำเร็จ โดยมีข้อผิดพลาดที่ระบุว่าการอัปโหลดถูกยกเลิก

Kotlin+KTX

uploadTask = storageRef.child("images/mountains.jpg").putFile(file)

// Pause the upload
uploadTask.pause()

// Resume the upload
uploadTask.resume()

// Cancel the upload
uploadTask.cancel()

Java

uploadTask = storageRef.child("images/mountains.jpg").putFile(file);

// Pause the upload
uploadTask.pause();

// Resume the upload
uploadTask.resume();

// Cancel the upload
uploadTask.cancel();

ติดตามความคืบหน้าในการอัปโหลด

คุณเพิ่ม Listener เพื่อจัดการกับความสำเร็จ ความล้มเหลว ความคืบหน้า หรือช่วงพักในภารกิจการอัปโหลดได้ ดังนี้

ประเภท Listener การใช้งานทั่วไป
OnProgressListener ระบบจะเรียกใช้ Listener นี้เป็นระยะๆ เมื่อมีการโอนข้อมูล และสามารถใช้เพื่อแสดงตัวบ่งชี้การอัปโหลด/ดาวน์โหลด
OnPausedListener ระบบจะเรียกใช้ Listener นี้ทุกครั้งที่งานหยุดชั่วคราว
OnSuccessListener ระบบจะเรียกฟังก์ชันนี้เมื่องานเสร็จสมบูรณ์
OnFailureListener ระบบจะเรียกใช้ Listener นี้ทุกครั้งที่การอัปโหลดไม่สำเร็จ ซึ่งอาจเกิดจากเครือข่ายหมดเวลา การให้สิทธิ์ไม่สำเร็จ หรือคุณยกเลิกงาน

เรียก OnFailureListener ด้วยอินสแตนซ์ Exception ส่วนตัวฟังอื่นๆ จะเรียกด้วยออบเจ็กต์ UploadTask.TaskSnapshot ออบเจ็กต์นี้เป็นมุมมองที่เปลี่ยนแปลงไม่ได้ของงาน ณ เวลาที่เกิดเหตุการณ์ UploadTask.TaskSnapshot มีพร็อพเพอร์ตี้ต่อไปนี้

พร็อพเพอร์ตี้ ประเภท คำอธิบาย
getDownloadUrl String URL ที่สามารถใช้ดาวน์โหลดออบเจ็กต์ ซึ่งเป็น URL สาธารณะที่คาดเดาไม่ได้ซึ่งแชร์กับไคลเอ็นต์รายอื่นได้ ระบบจะป้อนข้อมูลค่านี้เมื่อการอัปโหลดเสร็จสมบูรณ์
getError Exception หากงานไม่สำเร็จ รายการนี้จะระบุสาเหตุเป็น "ข้อยกเว้น"
getBytesTransferred long จํานวนไบต์ทั้งหมดที่โอนเมื่อถ่ายภาพหน้าจอนี้
getTotalByteCount long จำนวนไบต์ทั้งหมดที่คาดว่าจะอัปโหลด
getUploadSessionUri String URI ที่สามารถใช้เพื่อดําเนินการนี้ต่อผ่านการเรียกใช้ putFile อีกครั้ง
getMetadata StorageMetadata ข้อมูลเมตานี้จะถูกส่งไปยังเซิร์ฟเวอร์ก่อนที่การอัปโหลดจะเสร็จสมบูรณ์ หลังจากอัปโหลดเสร็จแล้ว ข้อมูลเมตาที่เซิร์ฟเวอร์แสดงผลจะเป็นข้อมูลต่อไปนี้
getTask UploadTask งานที่สร้างสแนปชอตนี้ ใช้งานนี้เพื่อยกเลิก หยุดชั่วคราว หรืออัปโหลดต่อ
getStorage StorageReference StorageReference ที่ใช้สร้าง UploadTask

UploadTask Listener เหตุการณ์เป็นวิธีที่ง่ายและมีประสิทธิภาพในการตรวจสอบเหตุการณ์การอัปโหลด

Kotlin+KTX

// Observe state change events such as progress, pause, and resume
// You'll need to import com.google.firebase.storage.component1 and
// com.google.firebase.storage.component2
uploadTask.addOnProgressListener { (bytesTransferred, totalByteCount) ->
    val progress = (100.0 * bytesTransferred) / totalByteCount
    Log.d(TAG, "Upload is $progress% done")
}.addOnPausedListener {
    Log.d(TAG, "Upload is paused")
}

Java

// Observe state change events such as progress, pause, and resume
uploadTask.addOnProgressListener(new OnProgressListener<UploadTask.TaskSnapshot>() {
    @Override
    public void onProgress(UploadTask.TaskSnapshot taskSnapshot) {
        double progress = (100.0 * taskSnapshot.getBytesTransferred()) / taskSnapshot.getTotalByteCount();
        Log.d(TAG, "Upload is " + progress + "% done");
    }
}).addOnPausedListener(new OnPausedListener<UploadTask.TaskSnapshot>() {
    @Override
    public void onPaused(UploadTask.TaskSnapshot taskSnapshot) {
        Log.d(TAG, "Upload is paused");
    }
});

จัดการการเปลี่ยนแปลงวงจรชีวิตของกิจกรรม

การอัปโหลดจะยังคงดำเนินต่อไปในเบื้องหลังแม้ว่าวงจรกิจกรรมจะเปลี่ยนแปลง (เช่น การแสดงกล่องโต้ตอบหรือการหมุนหน้าจอ) ผู้ฟังที่คุณแนบไว้จะยังคงแนบอยู่ด้วย ซึ่งอาจทำให้เกิดผลลัพธ์ที่ไม่คาดคิดหากมีการเรียกใช้หลังจากกิจกรรมหยุดลง

คุณสามารถแก้ปัญหานี้ได้โดยการติดตาม Listener ด้วยขอบเขตกิจกรรมเพื่อยกเลิกการลงทะเบียนโดยอัตโนมัติเมื่อกิจกรรมหยุดลง จากนั้นใช้เมธอด getActiveUploadTasks เมื่อกิจกรรมเริ่มขึ้นอีกครั้งเพื่อดูงานอัปโหลดที่ยังคงทำงานอยู่หรือเพิ่งเสร็จสิ้น

ตัวอย่างด้านล่างแสดงวิธีนี้และยังแสดงวิธีเก็บถาวรเส้นทางอ้างอิงพื้นที่เก็บข้อมูลที่ใช้

Kotlin+KTX

override fun onSaveInstanceState(outState: Bundle) {
    super.onSaveInstanceState(outState)

    // If there's an upload in progress, save the reference so you can query it later
    outState.putString("reference", storageRef.toString())
}

override fun onRestoreInstanceState(savedInstanceState: Bundle) {
    super.onRestoreInstanceState(savedInstanceState)

    // If there was an upload in progress, get its reference and create a new StorageReference
    val stringRef = savedInstanceState.getString("reference") ?: return

    storageRef = Firebase.storage.getReferenceFromUrl(stringRef)

    // Find all UploadTasks under this StorageReference (in this example, there should be one)

    val tasks = storageRef.activeUploadTasks

    if (tasks.size > 0) {
        // Get the task monitoring the upload
        val task = tasks[0]

        // Add new listeners to the task using an Activity scope
        task.addOnSuccessListener(this) {
            // Success!
            // ...
        }
    }
}

Java

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);

    // If there's an upload in progress, save the reference so you can query it later
    if (mStorageRef != null) {
        outState.putString("reference", mStorageRef.toString());
    }
}

@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
    super.onRestoreInstanceState(savedInstanceState);

    // If there was an upload in progress, get its reference and create a new StorageReference
    final String stringRef = savedInstanceState.getString("reference");
    if (stringRef == null) {
        return;
    }
    mStorageRef = FirebaseStorage.getInstance().getReferenceFromUrl(stringRef);

    // Find all UploadTasks under this StorageReference (in this example, there should be one)
    List<UploadTask> tasks = mStorageRef.getActiveUploadTasks();
    if (tasks.size() > 0) {
        // Get the task monitoring the upload
        UploadTask task = tasks.get(0);

        // Add new listeners to the task using an Activity scope
        task.addOnSuccessListener(this, new OnSuccessListener<UploadTask.TaskSnapshot>() {
            @Override
            public void onSuccess(UploadTask.TaskSnapshot state) {
                // Success!
                // ...
            }
        });
    }
}

getActiveUploadTasks จะดึงข้อมูลงานอัปโหลดที่ใช้งานอยู่ทั้งหมดที่ตรงกับหรือต่ำกว่าข้อมูลอ้างอิงที่ระบุ คุณจึงอาจต้องจัดการงานหลายรายการ

การอัปโหลดต่อเนื่องเมื่อกระบวนการเริ่มต้นใหม่

หากกระบวนการปิดลง การอัปโหลดที่อยู่ระหว่างดำเนินการจะหยุดชะงัก อย่างไรก็ตาม คุณจะอัปโหลดต่อได้เมื่อกระบวนการเริ่มต้นอีกครั้งโดยกลับมาดำเนินการกับเซสชันการอัปโหลดกับเซิร์ฟเวอร์ ซึ่งจะช่วยประหยัดเวลาและแบนด์วิดท์ได้โดยไม่ต้องเริ่มอัปโหลดจากต้นไฟล์

โดยเริ่มอัปโหลดผ่าน putFile ใน StorageTask ที่เป็นผลลัพธ์ ให้เรียกใช้ getUploadSessionUri และบันทึกค่าที่ได้ไว้ในพื้นที่เก็บข้อมูลถาวร (เช่น SharedPreferences)

Kotlin+KTX

uploadTask = storageRef.putFile(localFile)
uploadTask.addOnProgressListener { taskSnapshot ->
    sessionUri = taskSnapshot.uploadSessionUri
    if (sessionUri != null && !saved) {
        saved = true
        // A persisted session has begun with the server.
        // Save this to persistent storage in case the process dies.
    }
}

Java

uploadTask = mStorageRef.putFile(localFile);
uploadTask.addOnProgressListener(new OnProgressListener<UploadTask.TaskSnapshot>() {
    @Override
    public void onProgress(UploadTask.TaskSnapshot taskSnapshot) {
        Uri sessionUri = taskSnapshot.getUploadSessionUri();
        if (sessionUri != null && !mSaved) {
            mSaved = true;
            // A persisted session has begun with the server.
            // Save this to persistent storage in case the process dies.
        }
    }
});

หลังจากกระบวนการเริ่มต้นใหม่ด้วยการอัปโหลดที่ขัดจังหวะ ให้เรียกใช้ putFile อีกครั้ง แต่ครั้งนี้ให้ส่ง Uri ที่บันทึกไว้ด้วย

Kotlin+KTX

// resume the upload task from where it left off when the process died.
// to do this, pass the sessionUri as the last parameter
uploadTask = storageRef.putFile(
    localFile,
    storageMetadata { },
    sessionUri,
)

Java

//resume the upload task from where it left off when the process died.
//to do this, pass the sessionUri as the last parameter
uploadTask = mStorageRef.putFile(localFile,
        new StorageMetadata.Builder().build(), sessionUri);

เซสชันมีอายุ 1 สัปดาห์ หากพยายามกลับมาดำเนินการในเซสชันต่อหลังจากเซสชันหมดอายุหรือเกิดข้อผิดพลาด คุณจะได้รับแบ็กคอลที่ระบุว่าดำเนินการไม่สำเร็จ คุณมีหน้าที่รับผิดชอบในการตรวจสอบว่าไฟล์ไม่มีการเปลี่ยนแปลงระหว่างการอัปโหลด

การจัดการข้อผิดพลาด

ข้อผิดพลาดที่อาจเกิดขึ้นระหว่างการอัปโหลดมีสาเหตุหลายประการ เช่น ไฟล์ในเครื่องไม่อยู่ หรือผู้ใช้ไม่มีสิทธิ์อัปโหลดไฟล์ที่ต้องการ ดูข้อมูลเพิ่มเติมเกี่ยวกับข้อผิดพลาดได้ในส่วนจัดการข้อผิดพลาดของเอกสาร

ตัวอย่างแบบเต็ม

ตัวอย่างการอัปโหลดแบบเต็มที่มีการตรวจสอบความคืบหน้าและการจัดการข้อผิดพลาดแสดงอยู่ด้านล่าง

Kotlin+KTX

// File or Blob
file = Uri.fromFile(File("path/to/mountains.jpg"))

// Create the file metadata
metadata = storageMetadata {
    contentType = "image/jpeg"
}

// Upload file and metadata to the path 'images/mountains.jpg'
uploadTask = storageRef.child("images/${file.lastPathSegment}").putFile(file, metadata)

// Listen for state changes, errors, and completion of the upload.
// You'll need to import com.google.firebase.storage.component1 and
// com.google.firebase.storage.component2
uploadTask.addOnProgressListener { (bytesTransferred, totalByteCount) ->
    val progress = (100.0 * bytesTransferred) / totalByteCount
    Log.d(TAG, "Upload is $progress% done")
}.addOnPausedListener {
    Log.d(TAG, "Upload is paused")
}.addOnFailureListener {
    // Handle unsuccessful uploads
}.addOnSuccessListener {
    // Handle successful uploads on complete
    // ...
}

Java

// File or Blob
file = Uri.fromFile(new File("path/to/mountains.jpg"));

// Create the file metadata
metadata = new StorageMetadata.Builder()
        .setContentType("image/jpeg")
        .build();

// Upload file and metadata to the path 'images/mountains.jpg'
uploadTask = storageRef.child("images/"+file.getLastPathSegment()).putFile(file, metadata);

// Listen for state changes, errors, and completion of the upload.
uploadTask.addOnProgressListener(new OnProgressListener<UploadTask.TaskSnapshot>() {
    @Override
    public void onProgress(UploadTask.TaskSnapshot taskSnapshot) {
        double progress = (100.0 * taskSnapshot.getBytesTransferred()) / taskSnapshot.getTotalByteCount();
        Log.d(TAG, "Upload is " + progress + "% done");
    }
}).addOnPausedListener(new OnPausedListener<UploadTask.TaskSnapshot>() {
    @Override
    public void onPaused(UploadTask.TaskSnapshot taskSnapshot) {
        Log.d(TAG, "Upload is paused");
    }
}).addOnFailureListener(new OnFailureListener() {
    @Override
    public void onFailure(@NonNull Exception exception) {
        // Handle unsuccessful uploads
    }
}).addOnSuccessListener(new OnSuccessListener<UploadTask.TaskSnapshot>() {
    @Override
    public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) {
        // Handle successful uploads on complete
        // ...
    }
});

เมื่ออัปโหลดไฟล์แล้ว มาดูวิธีดาวน์โหลดไฟล์จาก Cloud Storage