How to remove pages from PDF Document in android with Apache PDFBox

Imagine you have a PDF file and you want to remove or delete a page(s) for example page 2 from a PDF document in android, how would you do that?

If you don’t have an idea how you can accomplish it then this tutorial is for you.

At the end of this tutorial you will not only learn how to remove a page from PDF document, you will have the skills to expand on what you have learnt to add more features to your project.

If you want to learn more about working with PDF and manipulating PDF document in Android, feel free to search our extensive PDF tutorials.

Below is the screenshot of the app we will create

1. CREATE A NEW ANDROID PROJECT

  • Open Android Studio
  • Go to file menu
  • Select new option
  • Enter project name
  • Enter activity name
  • Keep other default settings
  • Click on finish button to create a new android project

2. ADD PERMISSION

Since accessing android storage API needs permission, we are going to add this code in our project manifest file.

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

3. ADD ANDROID THIRD-PARTY LIBRARIES

We are going to add few android libraries that will help us solve some problems rather than reinventing the wheels.

Open your project build.gradle and add the libraries below.

dependencies {
    implementation fileTree(dir: "libs", include: ["*.jar"])
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    implementation 'com.google.android.material:material:1.3.0-alpha02'

    //loading
    implementation 'com.github.d-max:spots-dialog:1.1@aar'

    //RxAndroid
    implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
    implementation "io.reactivex.rxjava2:rxandroid:2.1.1"
    implementation "io.reactivex.rxjava2:rxjava:2.2.10"

    //Pdf viewer
    implementation 'com.github.barteksc:android-pdf-viewer:3.2.0-beta.1'

    //PDFBox android
    implementation 'com.tom_roush:pdfbox-android:1.8.10.1'
    //File picker
    implementation 'com.github.jaiselrahman:FilePicker:1.3.2'

    implementation 'com.jakewharton:butterknife:10.2.1'
    annotationProcessor "com.jakewharton:butterknife-compiler:10.2.1"

    implementation 'com.karumi:dexter:5.0.0'

    //Lombok
    compileOnly 'org.projectlombok:lombok:1.18.8'
    annotationProcessor 'org.projectlombok:lombok:1.18.8'

    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.1'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}

4. CREATE A NEW ACTIVITY CLASS

Create a new activity pages and name it RemovePageActivity or any name of your choice.

Open the xml layout file of the activity class and paste the code below.

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="18dp"
    tools:context=".remove.RemovePageActivity">

    <androidx.appcompat.widget.AppCompatButton
        android:id="@+id/select_pdf_btn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/colorPrimaryDark"
        android:text="SELECT PDF FILE"
        android:textColor="@color/colorWhite"
        app:layout_constraintTop_toTopOf="parent"/>

    <com.google.android.material.textfield.TextInputLayout
        android:id="@+id/page_number_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:textColorHint="@color/colorAccent"
        app:layout_constraintTop_toBottomOf="@id/select_pdf_btn">

        <com.google.android.material.textfield.TextInputEditText
            android:id="@+id/page_number"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="Page Number"
            android:textColor="@color/colorAccent"
            android:textSize="15sp"
            android:digits="0123456789,"
            android:inputType="number"/>

    </com.google.android.material.textfield.TextInputLayout>

    <androidx.appcompat.widget.AppCompatTextView
        android:id="@+id/extract_hint"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="*Insert commas for multiple numbers(e.g 4,8,12). Each file will start from these page numbers."
        android:textSize="16sp"
        android:textColor="@color/colorAccent"
        android:gravity="center"
        app:layout_constraintTop_toBottomOf="@id/page_number_layout"/>

    <androidx.cardview.widget.CardView
        android:id="@+id/card_wrapper"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="2dp"
        app:cardElevation="2dp"
        app:cardUseCompatPadding="true"
        android:layout_marginTop="15dp"
        android:visibility="gone"
        app:layout_constraintTop_toBottomOf="@id/extract_hint">

        <androidx.appcompat.widget.LinearLayoutCompat
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            android:gravity="center"
            android:padding="4dp">

            <androidx.appcompat.widget.AppCompatImageView
                android:layout_width="match_parent"
                android:layout_height="40dp"
                android:scaleType="fitXY"
                android:src="@drawable/pdf"
                android:layout_weight="1.1"
                android:layout_marginEnd="5dp"/>

            <androidx.appcompat.widget.AppCompatTextView
                android:id="@+id/pdf_name"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="Details.pdf"
                android:textColor="@color/colorBlack"
                android:textSize="17sp"
                android:layout_weight="0.3"
                android:singleLine="true"
                android:ellipsize="end"
                android:layout_marginEnd="5dp"/>

            <androidx.appcompat.widget.AppCompatImageView
                android:id="@+id/action_delete"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_marginTop="2dp"
                android:scaleType="fitCenter"
                android:src="@drawable/ic_delete_24dp"
                android:layout_weight="1.2"/>

        </androidx.appcompat.widget.LinearLayoutCompat>

    </androidx.cardview.widget.CardView>

  <com.google.android.material.floatingactionbutton.FloatingActionButton
        android:id="@+id/extract_btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="16dp"
        android:src="@drawable/ic_done"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

Before we proceed to get the references of the View components in the layout file above, we are going to create a few other java classes.

5. CREATE A NEW JAVA MODEL CLASS

Create a new Java class and name it PdfToolsModel.java or any name of your choice.

This class will model the PDF document object. Open the class and paste the code before.

@Setter
@Getter
public class PdfToolsModel {

    private Boolean fileSave;

    private String filePath;

    private List<PDPage> pdPageList;

    private Boolean success;

    private String title;

    private String author;

    private String creator;

    private String producer;

    private String subject;

    private String keywords;

    private Calendar creationDate;

    private Calendar modificationDate;
}

6. CREATE A NEW JAVA HELPER CLASS

Create a new Java class and name it ExtraPageHelper.java or any name of your choice.

This class will contain the main method that extracts pages from a PDF document.

If you have followed all our PDF tutorial series using Apache PDFBox, then it will be easy to understand the content of the method.

public class ExtractPageHelper {

    private static final String TAG = ExtractPageHelper.class.getSimpleName();

    private File directory;

    private Context context;

    public static final String FOLDER_NAME = "/PDF/Doc";

    public ExtractPageHelper(Context context) {
        this.context = context;
        directory = context.getExternalFilesDir(FOLDER_NAME );
        if (directory != null) {
            if (!directory.exists()) {
                directory.mkdir();
            }
        } else {
            Log.d(TAG, "Not working yet");
        }
    }

    public PdfToolsModel extractPagePdfFile(File pdfInputFile, String pageNumberInput) {
        PdfToolsModel pdfToolsModel = new PdfToolsModel();
        try {
            PDDocument pdDocument = PDDocument.load(pdfInputFile);
            List<PDPage> pdPageList = pdfPageToListConvert(pdDocument);
            List<String> pageSplitList = Arrays.asList(pageNumberInput.split(","));
            PDDocument pagePdDocument = new PDDocument();

            for (int i = 0; i < pdPageList.size(); i++) {
                int pdfPageIndex = i + 1;
                if (pageSplitList.contains(String.valueOf(pdfPageIndex))) {
                    PDPage pdPage = pdPageList.get(i);
                    copyPdfPage(pdPage, pagePdDocument);
                }
            }
            savePdfFile(pdfInputFile, pagePdDocument, pdfToolsModel);
        } catch (IOException e) {
            pdfToolsModel.setFileSave(false);
            e.printStackTrace();
        }
        return pdfToolsModel;
    }

    private List<PDPage> pdfPageToListConvert(PDDocument pdDocument) {
        List<PDPage> pdPageList = new ArrayList<>();
        pdDocument.getDocumentCatalog().getPages().iterator().forEachRemaining(pdPageList::add);
        return pdPageList;
    }

    private void copyPdfPage(PDPage pdPage, PDDocument pdDocument) {
        COSDictionary cosDictionary = pdPage.getCOSObject();
        COSDictionary newCosDictionary = new COSDictionary(cosDictionary);
        newCosDictionary.removeItem(COSName.ANNOTS);
        PDPage newPage = new PDPage(newCosDictionary);
        pdDocument.addPage(newPage);
    }

    private PdfToolsModel savePdfFile(File pdfInputFile, PDDocument pdDocument, PdfToolsModel pdfToolsModel) throws IOException {
        File newFilePath = new File(directory, pdfInputFile.getName());
        pdDocument.save(newFilePath);
        pdDocument.close();
        pdfToolsModel.setFileSave(true);
        pdfToolsModel.setFilePath(newFilePath.toString());
        return pdfToolsModel;
    }
}

7. CREATE A NEW VIEW MODEL CLASS

Create a new Java class and name it ExtraPageViewModel.java. Let the new class extends from ViewModel class.

Open the View model class and paste the code below.

@Setter
@Getter
public class ExtractPageViewModel extends ViewModel {

    private static final String TAG = ExtractPageViewModel.class.getSimpleName();

    private MutableLiveData<PdfToolsModel> extractPageLiveData;

    public ExtractPageViewModel() {
        extractPageLiveData = new MutableLiveData<>();
    }

    public void extractPagePdf(ExtractPageHelper pdfToolsHelper, File pdfInputFile, String pageNumberInput) {
        Single.create(new SingleOnSubscribe<PdfToolsModel>() {
            @Override
            public void subscribe(SingleEmitter<PdfToolsModel> emitter) throws Exception {
                PdfToolsModel pdfResult = pdfToolsHelper.extractPagePdfFile(pdfInputFile, pageNumberInput);
                emitter.onSuccess(pdfResult);
            }
        }).subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new SingleObserver<PdfToolsModel>() {
                    @Override
                    public void onSubscribe(Disposable d) {
                    }

                    @Override
                    public void onSuccess(PdfToolsModel pdfToolsModel) {
                        if(pdfToolsModel != null){
                            extractPageLiveData.postValue(pdfToolsModel);
                        }
                    }

                    @Override
                    public void onError(Throwable e) {
                        Log.e(TAG, e.getMessage());
                    }
                });
    }
}

8. Let Complete Our Activity Page

Now that we have all the pieces of the code ready, we are going to connect them together.

When a user click on Select PDF file button, a File picker will open for the user to select a PDF file of choice.

The EditText view let the user to add the page number(s) that should be removed.

When all the selection is done, and the FAB button is click, the page removal from PDF document is processed in a background thread.

The user is notified once the process is completed.

Open the activity class and add the code below.

public class RemovePageActivity extends AppCompatActivity {

    private static final String TAG = RemovePageActivity.class.getSimpleName();

    @BindView(R.id.page_number)
    TextInputEditText pageNumberBox;

    @BindView(R.id.card_wrapper)
    CardView cardWrapper;

    @BindView(R.id.pdf_name)
    AppCompatTextView pdfName;

    private ExtractPageViewModel viewModel;

    private ExtractPageHelper pdfToolsHelper;

    private AlertDialog progressAlert;

    private File pdfInputFile = null;

    public static int PDF_REQUEST_CODE = 476;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_remove_page);

        ButterKnife.bind(this);

        ActionBar actionBar = getSupportActionBar();
        if (actionBar != null){
            actionBar.setTitle("Extract Page From PDF");
        }

        viewModel = ViewModelProviders.of(this).get(ExtractPageViewModel.class);

        pdfToolsHelper = new ExtractPageHelper(RemovePageActivity.this);

        progressAlert = new SpotsDialog.Builder().setContext(this).build();

        viewModelObserver();
    }

    @OnClick(R.id.select_pdf_btn)
    void selectPdfClick() {
        openPdf();
    }

    @OnClick(R.id.action_delete)
    void actionDeletePdf() {
        pdfInputFile = null;
        pageNumberBox.setText("");
        cardWrapper.setVisibility(View.GONE);
    }

    @OnClick(R.id.extract_btn)
    void splitPdfClick() {
        String pageNumberInput = Objects.requireNonNull(pageNumberBox.getText()).toString();
        if (pdfInputFile == null || TextUtils.isEmpty(pageNumberInput)) {
            Toast.makeText(RemovePageActivity.this, "Please select pdf file or input a page nummber", Toast.LENGTH_SHORT).show();
        } else {
            progressAlert.show();
            viewModel.extractPagePdf(pdfToolsHelper, pdfInputFile, pageNumberInput);
        }
    }

    private void openPdf() {
        String[] suffix = {"pdf", "Pdf", "PDF"};
        Intent intent = new Intent(RemovePageActivity.this, FilePickerActivity.class);
        intent.putExtra(FilePickerActivity.CONFIGS, new Configurations.Builder()
                .setCheckPermission(true)
                .setShowImages(false)
                .setShowAudios(false)
                .setShowVideos(false)
                .setShowFiles(true)
                .enableImageCapture(false)
                .setMaxSelection(1)
                .setSkipZeroSizeFiles(true)
                .setSuffixes(suffix)
                .build());
        startActivityForResult(intent, PDF_REQUEST_CODE);
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (resultCode == RESULT_OK && requestCode == PDF_REQUEST_CODE) {
            if (data != null) {
                ArrayList<MediaFile> mediaFileArrayList = data.getParcelableArrayListExtra(FilePickerActivity.MEDIA_FILES);
                if (mediaFileArrayList != null) {
                    for (MediaFile mediaFile : mediaFileArrayList) {
                        pdfInputFile = new File(getPath(RemovePageActivity.this, mediaFile.getUri()));
                    }
                    cardWrapper.setVisibility(View.VISIBLE);
                    pdfName.setText(pdfInputFile.getName());
                } else {
                    cardWrapper.setVisibility(View.GONE);
                }
            }
        }
    }

    public static String getPath(Activity activity, Uri uri) {
        String[] projection = {MediaStore.Images.Media.DATA};
        Cursor cursor = activity.getContentResolver().query(uri, projection, null, null, null);
        if (cursor == null) return null;
        int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
        cursor.moveToFirst();
        String s = cursor.getString(column_index);
        cursor.close();
        return s;
    }

    private void viewModelObserver(){
        viewModel.getExtractPageLiveData().observe(this, new Observer<PdfToolsModel>() {
            @Override
            public void onChanged(PdfToolsModel pdfToolsModel) {
                progressAlert.dismiss();
                if(pdfToolsModel.getFileSave()){
                    Toast.makeText(RemovePageActivity.this, "Page successfully removed", Toast.LENGTH_SHORT).show();
                }else{
                    Toast.makeText(RemovePageActivity.this, "Opps pdf is password protected", Toast.LENGTH_SHORT).show();
                }
            }
        });
    }

    @Override
    public boolean onOptionsItemSelected(@NonNull MenuItem item) {
        int id = item.getItemId();
        if (id == android.R.id.home){
            onBackPressed();
        }
        return super.onOptionsItemSelected(item);
    }

    @Override
    public void onBackPressed() {
        super.onBackPressed();
    }
}

In subsequent tutorials, we are going to cover how to manipulate PDF in android like merging multiple PDFs, compressing PDF and so on.

If you have any questions about working with PDF file in Android kindly use the comment box below and drop your questions.

Add a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.