Image by mamewmy on Freepik

Creating Dynamic PDF Documents with Android: Step-by-Step Tutorial for Dynamic PDFs

Meet
7 min readJun 15, 2023

--

Android provides a solid framework for creating PDFs from native content. In this blog post, we’ll look at how to make a PDF document from scratch using the native Android framework.

The Android OS includes a specialized PDF-generating class called PdfDocument. This class enables developers to easily create, alter, and export PDF files. The process starts with the creation of a new document, followed by the addition of pages, the addition of content to each page, and ultimately the saving and closure of the document.

How does it work?

Using the PdfDocument Class
The first step is to use the PdfDocumentclass, which is responsible for creating and rendering PDF files. This class contains methods and attributes for manipulating PDF content.

Providing the Data
To render the PDF, you need to provide the data that will be displayed in the PDF document. This data can be retrieved from various sources, such as a database, API, or user input.

Defining Page Dimensions and Layout
Before rendering the content, you need to define the page dimensions and layout. Determine the height and width of each page based on your requirements. Additionally, calculate the number of pages needed based on the total items and the desired number of items per page.

Rendering Views from XML Files
To populate the PDF document with content, you can utilize XML layout files. Create XML files representing the desired views and content that you want to display in the PDF document. You can design these XML similar to how you design regular app layouts.

Use the PdfDocument class to render these views onto the PDF document. Iterate through the array of data and create the corresponding views dynamically. Then, for each page, create a canvas using the PdfDocument class and render the views onto the canvas.

Let’s Get Started

We will see the simple app that tracks the daily expenses of the user and list them date-wise. We will see how we can generate or render the pdf for the same.

Here is the demo,

Creating a PdfDocument instance:

/**Generate Pdf Document**/
val doc = PdfDocument()
/**Generate Page Info For The Page**/
val pageInfo = PdfDocument.PageInfo.Builder(PDF_PAGE_WIDTH, PDF_PAGE_HEIGHT, 1).create()

Next, we generate PageInfo for the page that will be added to the PDF document. The PageInfo object specifies the dimensions and other properties of the page.

/**Dimension For A4 Size Paper (1 inch = 72 points)**/
private const val PDF_PAGE_WIDTH = 595 //8.26 Inch
private const val PDF_PAGE_HEIGHT = 842 //11.69 Inch

Here’s what each parameter means:

  • PDF_PAGE_WIDTH: This constant represents the width of the page in points (1 inch = 72 points).
  • PDF_PAGE_HEIGHT: This constant represents the height of the page in points (1 inch = 72 points).
  • 1: This parameter represents the page number. In this case, we are creating the first page of the PDF document.

The PdfDocument.PageInfo.Builder class provides a convenient way to set the page dimensions and other properties. We pass the width, height, and page number to the Builder, and then call the create() method to generate the PageInfo object.

The PageInfo object will be used when adding content to the PDF document’s pages.

Creating the first page of the PDF:

/**Get all the items for the dateRange**/
val spendingItems = spendingDao.getSpendingByDateRangeReportPdf(startDate, endDate)
/**Calculate the total amount of spending**/
val totalAmount = spendingItems.sumOf { it.amount }
/**Total Items**/
val totalItem = spendingItems.size
/**Parent For SpendingItem's View**/
val parentView = parentView()
/**Header For the first page**/
val pdfHeaderView = pdfHeaderView(startDate, endDate, totalAmount, totalItem)
/**Add Header on top in the parent**/
parentView.addView(pdfHeaderView)
/**Make First Page For Summary**/
val page = doc.startPage(pageInfo)
/**Render Page Fro First Page**/
loadBitmapFromView(parentView)?.let {
page.canvas.drawBitmap(
it,
0f,
0f,
null
)
}
/**Finish the first page and remove all the views**/
doc.finishPage(page)
parentView.removeAllViews()

Here we fetch the data from the database, calculate the total items and prepare the views.

parentView() function to create a parent view that will hold the other views.

private fun parentView(): LinearLayout {
val parentView = LinearLayout(context, null, R.style.Theme_MySpending)
val params: ViewGroup.LayoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
parentView.layoutParams = params
parentView.orientation = LinearLayout.VERTICAL
return parentView
}

pdfHeaderView function to create a header view for the PDF document's first page. This view typically contains information such as the date range, total amount, and total number of items.

private fun pdfHeaderView(startDate: Date, endDate: Date, totalAmount: Long, totalItems: Int) =
with(
ViewPdfHeaderBinding.inflate(
LayoutInflater.from(context)
.cloneInContext(ContextThemeWrapper(context, R.style.Theme_MySpending))
)
) {
dateRangeText.text = context.getString(
R.string.date_range,
DateUtil.getFormattedDate(startDate),
DateUtil.getFormattedDate(endDate)
)
this.totalAmount.text = context.getString(
R.string.rupee_symbol,
totalAmount.toString()
)
totalItem.text = totalItems.toString()
this.root
}

Important Note While Creating XML Layouts:

For better sizing, we use px font size and dimension. px renders better and looks real when PDF renders. We can also use dp it in the layout and later in the code, we can convert it px if we want.

We can later programmatically inflate the view while rendering the PDF just like we did with pdfHeaderView.

Rendering Layout XML:

The startPage method of the PdfDocument object (doc) is called with the pageInfo object as a parameter to initiate the creation of the first page of the PDF document. This method returns a PdfDocument.Page is an object representing the newly created page.

Now that we have created the page object we can draw the view on it. For that, we can use one function that returns the Bitmap from the view. So we can easily use that to draw.

private fun loadBitmapFromView(v: View): Bitmap? {
val specWidth: Int =
View.MeasureSpec.makeMeasureSpec(PDF_PAGE_WIDTH, View.MeasureSpec.EXACTLY)
val specHeight: Int =
View.MeasureSpec.makeMeasureSpec(PDF_PAGE_HEIGHT, View.MeasureSpec.EXACTLY)
v.measure(specWidth, specHeight)
val requiredWidth: Int = v.measuredWidth
val requiredHeight: Int = v.measuredHeight
val b = Bitmap.createBitmap(requiredWidth, requiredHeight, Bitmap.Config.ARGB_8888)
val c = Canvas(b)
c.drawColor(Color.WHITE)
v.layout(v.left, v.top, v.right, v.bottom)
v.draw(c)
return b
}
doc.finishPage(page)
parentView.removeAllViews()

The finishPage method is called to indicate that the rendering of the first page is complete. After finishing the page, the code removes all child views from the parentView to prepare for rendering subsequent pages.

Here is the result for the first header page,

Rendering Multiple Pages

As we have a list of items from the database to render, we have to determine how many pages are required to be rendered in our PDF.

Steps:

  1. Calculates the total number of pages required for the PDF based on the number of spending items in the list.
  2. Sets the page capacity (ITEM_CAPACITY_PER_PAGE = 25) for each page, ensuring it does not exceed the number of spending items.
  3. Iterates over the list of spending items paginate them, and adds them to a parent view.
  4. Renders each page as a bitmap image and adds it to the PDF document.
  5. Clears the parent view after each page is added to the PDF.

we can use this simple function to know the exact pages required from the count of the items.

  private fun getRequiredPage(totalItems: Int): Int {
return ceil(totalItems.toDouble() / ITEM_CAPACITY_PER_PAGE).toInt()
}
/**Find total page required for the pdf**/
val totalPage = getRequiredPage(spendingItems.size)
var pageNum = 0

/**If the total item is less than pageCapacity then we take the item size as new capacity**/
val finalPageCapacity =
if ( ITEM_CAPACITY_PER_PAGE > spendingItems.size) spendingItems.size else ITEM_CAPACITY_PER_PAGE

Now we have the count of the required pages, we can paginate the list of items and create the views from it and add them to the page.

/**We paginated the list and add them to parentView**/
while (pageNum < totalPage) {
val currentPageItems = spendingItems.subList(
pageNum * finalPageCapacity,
(++pageNum * finalPageCapacity).coerceAtMost(spendingItems.size)
)
currentPageItems.forEachIndexed { index, spending ->
parentView.addView(
spendingItemView(
spending.purpose,
spending.amount,
spending.date,
index % 2 == 0
)
)
}

/**After adding items to view we render the new page and add it to pdf **/
val dynamicPage= doc.startPage(pageInfo)
loadBitmapFromView(parentView)?.let {
val c = dynamicPage.canvas
c.drawBitmap(
it,
0f,
20f,
null
)
}
doc.finishPage(dynamicPage)
parentView.removeAllViews()
}

The code enters a while loop that iterates over each page until pageNum reaches the totalPage count. Inside the loop, it creates a sublist of spending items for the current page based on the pageNum and finalPageCapacity values. It then adds each spending item to the parentView using the spendingItemView() function.

After adding the spending items to the parentView, the code creates a new page (dynamicPage) in the PDF document using the provided pageInfo. It converts the parentView into a bitmap image using the loadBitmapFromView() function and draws the bitmap on the canvas of dynamicPage. Finally, it finishes the page and removes all views from the parentView.

Here is the result for the second page with multiple items,

Writing the PDF in the storage

/**Write pdf to storage**/
val reportFolder = File(context.filesDir, "report")
if (!reportFolder.exists())
reportFolder.mkdir()
val file = File(
reportFolder,
"Report_${DateUtil.getFormattedDate(startDate)}_to_${
DateUtil.getFormattedDate(
endDate
)
}.pdf"
)
doc.writeTo(file.outputStream())
doc.close()

Writing the PDF to the storage is very easy. We created a folder called “report”. Created the file Object with an appropriate name.

The code calls the writeTo() method on the doc object, which represents the generated PDF document. It writes the content of the document to the specified file using the file.outputStream() to obtain an output stream for the file.

Finally, the close() method called on the doc object to release any resources associated with the PDF document, ensuring proper closure and cleanup.

Conclusion

By following the steps outlined above, you can render PDF documents in an Android application using the PdfDocument native class. The PdfDocument class allows you to dynamically create and modify PDF documents, making it a versatile tool for displaying data in PDF format.

Thanks for reading this article❤️. If you found this article useful then hit that clap button 👏. Do follow for more! Happy Coding❤️.

--

--

Meet

Full-time Android Developer, Tech Enthusiast, Knows Flutter, React Native, Web Development Etc…