How to create and add content to PDF File with Apache PDFBox in Android

In our last tutorial on working with PDF file in Android using Apache PDFBox, we covered in details how to read a PDF file in android programmatically.

Today, we will learn how to create a new PDF file and add content to the PDF file.

If you have not read my previous tutorial, I will suggest you first read the tutorial before you proceed with this tutorial.

Below is the screenshot of the app we will create

Write PDF in Android

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'
}

I have add more libraries like RxJava, RxAndroid, PDFViewer and ProgressView.

4. CREATE A NEW ACTIVITY CLASS

Create a new activity pages and name it WritePdfActivity 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.appcompat.widget.LinearLayoutCompat 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:orientation="vertical"
    android:background="#f3f3f3"
    android:padding="12dp"
    tools:context=".WritePdfActivity">

    <androidx.appcompat.widget.AppCompatEditText
        android:id="@+id/content_box"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="8dp"
        android:inputType="text"
        android:hint="Enter PDF content"/>

    <androidx.appcompat.widget.AppCompatButton
        android:id="@+id/create_pdf"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/colorAccent"
        android:textColor="@color/colorWhite"
        android:layout_marginTop="12dp"
        android:text="CREATE PDF"/>

    <com.github.barteksc.pdfviewer.PDFView
        android:id="@+id/pdf_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginTop="12dp"/>

</androidx.appcompat.widget.LinearLayoutCompat>

The layout file contains an EditText, Button and PDFView.

When a user enters a text and click on create PDF button, the text will be returned and used to create a new PDF file.

5. CREATE A NEW ViewModel CLASS

Next, we will create a new Java class that will inherit from Android ViewModel class.

We will create a method that will accept a text and file path as parameters.

Open the created Java class and paste the code below.

@Setter
@Getter
public class PDFViewModel extends ViewModel {

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

    private CompositeDisposable compositeDisposable;

    private MutableLiveData<Boolean> isLoading;

    public PDFViewModel() {
        compositeDisposable = new CompositeDisposable();
        isLoading = new MutableLiveData<>(false);
    }

    public void writeToPDFFile(String pdfFilePath, String pdfContent){
        Completable.create(new CompletableOnSubscribe() {
            @Override
            public void subscribe(CompletableEmitter emitter) throws Exception {
                openAndWriteToPDFFile(pdfFilePath, pdfContent);
                emitter.onComplete();
            }
        }).subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new CompletableObserver() {
            @Override
            public void onSubscribe(Disposable d) {
            }

            @Override
            public void onComplete() {
                isLoading.setValue(true);
                Log.d(TAG, "Completed");
            }

            @Override
            public void onError(Throwable e) {
                Log.d(TAG, "Error message " + e.getLocalizedMessage());
            }
        });
    }

    private void openAndWriteToPDFFile(String pdfFilePath, String pdfContent){
        PDDocument pdDocument = new PDDocument();
        PDPage page = new PDPage();
        pdDocument.addPage(page);

        try {
            PDPageContentStream contentStream = new PDPageContentStream(pdDocument, page);
            contentStream.beginText();
            contentStream.setFont(PDType1Font.COURIER_BOLD, 18);
            contentStream.setLeading(16f);
            contentStream.newLineAtOffset(30, 600);
            contentStream.showText(pdfContent);
            contentStream.endText();
            contentStream.close();

            Log.d(TAG, "File Path Log " + pdfFilePath);
            pdDocument.save(new File(pdfFilePath));

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

We have reactive programming to call the PDF method so that the UI thread will not be blocked.

6. Open Activity CLASS

In the activity class we are get all references to our layout views.

We will attach onclick event to the button view. Once the user entered text and click the button, the writeToPDFFile(String pdfFilePath, String pdfContent) method is called.

The complete code for the page is shown below.

public class WritePdfActivity extends AppCompatActivity {

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

    private File fileStorage;

    private static String FILE_PATH_NAME;

    private PDFViewModel viewModel;

    @BindView(R.id.content_box)
    AppCompatEditText contentBox;

    @BindView(R.id.pdf_view)
    PDFView pdfView;

    private AlertDialog progressView;

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

        ButterKnife.bind(this);
        permissionRequest();

        ActionBar actionBar = getSupportActionBar();
        if (actionBar != null){
            actionBar.setTitle("Create New PDF File");
        }
        PDFBoxResourceLoader.init(this);

        viewModel = ViewModelProviders.of(this).get(PDFViewModel.class);
        progressView = new SpotsDialog.Builder().setContext(this).setCancelable(false).setMessage("Processing...").build();

        fileStorage = getExternalFilesDir("PDF");
        if (fileStorage != null){
            if(!fileStorage.exists()){
                fileStorage.mkdir();
            }
            FILE_PATH_NAME = fileStorage.getAbsolutePath() + File.separator + "text.pdf";
        }
    }

    private void permissionRequest() {
        Dexter.withActivity(this).withPermissions(Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE)
                .withListener(new MultiplePermissionsListener() {
                    @Override
                    public void onPermissionsChecked(MultiplePermissionsReport report) {
                        if (report.areAllPermissionsGranted()) {
                            //do work when permission is granted
                        }else if (report.isAnyPermissionPermanentlyDenied()) {
                            Log.d(TAG, "Permission has been granted");
                        }
                    }
                    public void onPermissionRationaleShouldBeShown(List<PermissionRequest> permissions, PermissionToken token) {
                        token.continuePermissionRequest();
                    }
                })
                .withErrorListener(new PermissionRequestErrorListener() {
                    @Override
                    public void onError(DexterError error) {
                        Log.e(TAG, error.toString());
                    }
                })
                .onSameThread()
                .check();
    }

    @OnClick(R.id.create_pdf)
    public void createNewPDF(View view){
        String content = Objects.requireNonNull(contentBox.getText()).toString();
        if (TextUtils.isEmpty(content)){
            Toast.makeText(this, "Content box must be filled", Toast.LENGTH_SHORT).show();
        }else{
            writeNewPDFContent(pdfView, content);
        }
    }

    private void writeNewPDFContent(PDFView pdfView, String pdfContent){
        progressView.show();
        viewModel.writeToPDFFile(FILE_PATH_NAME, pdfContent);
        viewModel.getIsLoading().observe(this, new Observer<Boolean>() {
            @Override
            public void onChanged(Boolean aBoolean) {
                if (aBoolean){
                    // View the new PDF file
                    progressView.dismiss();
                    getPdfConfigurator(pdfView, FILE_PATH_NAME);
                }else{
                    progressView.dismiss();
                    Log.d(TAG, "Something has gone wrong");
                }
            }
        });
    }

    public void getPdfConfigurator(PDFView pdfView, String filePath) {
        PDFView.Configurator configurator = null;
        File file = new File(filePath);
        if (file.exists()){
            configurator = pdfView.fromFile(new File(filePath))
                    .enableDoubletap(true)
                    .enableSwipe(true)
                    .swipeHorizontal(true)
                    .enableAnnotationRendering(true)
                    .pageFitPolicy(FitPolicy.BOTH)
                    .fitEachPage(true)
                    .spacing(2);
        }
        if (configurator != null){
            configurator.load();
        }
    }
}

In subsequent tutorials, we are going to cover how to manipulate PDF in android like merge multiple PDFs, compress 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.