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.
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.