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