Yeni bir Android Projesini Empty Compose Activity ile Kotlin dili üzerinden oluşturduktan sonra PDF Generator üretme işleminin sıralı aşamalarını sizlerle paylaşmaktan mutluluk duyarım.
ui.theme paketi altında bulunan Color.kt adlı dosya üzerinde renk tanımlamalarımızı yapalım;
val turquaseColor = Color(0xFF30AAA5)
tanımlamasını ilave edelim.
Oluşturacağımız PDF Dosyası üzerinde resim gösterebilmek için projemizin res > drawable klasörü altına bir jpg veya png formatlı resim dosyasını ekleyiniz. Ben starwars adlı bir .png uzantılı resim ekledim 🙂

Uygulamamızın gerekli tanımlamalarını içeren izinleri için AndroidManifest.xml de belirtilmesini sağlıyoruz.
Uygulamamız en fazla dosya okuma ve dosya içeriğine yazma eylemleri gerçekleştireceğinden AndroidManifest.xml de tanımlanması gereken satırlar şöyle olacaktır;
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
MainActivity.kt dosyası üzerinde gerekli kodlarımızın tanımlanması için çalışmaya başlayalım;
setContent bloğu içerisinde Scaffold oluşturup bir toolbar üretip, renk olarak turquaseColor’ı tanımladım;
setContent {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
Scaffold(
topBar = {
TopAppBar(
backgroundColor = turquaseColor,
title = {
Text(
text = "MERHABA",
modifier = Modifier.fillMaxWidth(),
textAlign = TextAlign.Center,
color = Color.White
)
}
)
}
) {
GeneratePdfFile()
}
}
}
burada gördüğümüz kod bloğundaki GeneratePdfFile adlı fonksiyonu şimdi oluşturalım;
bir compose fonksiyonu oluşturmak için @Composable annotation’ını kullanıyoruz;
@Composable
fun GeneratePdfFile(){
}
fonksiyonumuza argüman olarak context, pageWidth, pageHeight, documentTitle, characters, documentName adlı değişkenleri tanımlayalım.
@Composable
fun GeneratePdfFile(
context: Context,
pageWidth: Int,
pageHeight: Int,
documentTitle: String,
characters: ArrayList<String>,
documentName: String
) {
}
Pdf dokümanı oluşturabilmemiz için PdfDocument (android.graphics.pdf paketinde API 32 Platformu ile gelen) sınıfını kullanacağız.
var pdfDoc: PdfDocument = PdfDocument()
//buradaki PdfDocument sınıfı Pdf dökümanı oluşturan sınıfımızdır.
paint ve title adlarında 2 adet Paint (android.graphics.Paint, API 32) nesnesi üretelim. Bu nesnelerden paint, herhangi bir resim dosyasını pdf içerisine ekleyebilmek, title ise pdf’e yazı yazmak amaçlı kullanılmış olacaktır.
var paint: Paint = Paint()
var title: Paint = Paint()
şimdi logoBitmap adlı bir variable oluşturup Bitmap tipinde dönmesini sağlayarak drawable klasörü içerisindeki bir resim dosyasının içeriğini değişkende saklayalım. Ben indirdiğim starwars dosyasını drawable klasörüne atmıştım bu nedenle R.drawable.starwars ile doğrudan nesneyi çağırabildim.
var logoBitmap: Bitmap = BitmapFactory.decodeResource(
context.resources,
R.drawable.starwars
)
logoBitmap’de çağırdığım görsel indirdiğim görselle birebir aynı boyutta olacağı için bir variable daha oluşturup bu Bitmap nesnesini ise küçültülmüş resim olacak şekilde ayrı bir değişkende saklayalım.
var scaledBitmap: Bitmap = Bitmap.createScaledBitmap(logoBitmap, 300, 181, false)
//scaledBitmap, logoBitmap'in ölçülendirilmiş halini yakalamak için üretildi.
Bitmap.createScaledBitmap içerisinde logoBitmap orijinal görseli, 300 genişliği, 181 yüksekliği temsil etmektedir.
Oluşturduğumuz pdfDoc adlı variable ile PdfDocument üretmiştik ancak bu dökümanın kaç sayfadan oluşacağı, sayfa boyutları gibi parametreleri tanımlamamıştık. Şimdi bu tanımlamalara geçelim;
var pdfPageInfo: PdfDocument.PageInfo? =
PdfDocument.PageInfo.Builder(pageWidth, pageHeight, 1).create()
//burada tek sayfadan oluşan bir pdf doküman oluşturduğumuzu belirtiyoruz.
pageWidth ve pageHeight değerleri doğrudan fonksiyon parametresinden gelecektir.
var ourPage: PdfDocument.Page = pdfDoc.startPage(pdfPageInfo)
//başlangıç sayfasını temsil ediyoruz.
var canvas: Canvas = ourPage.canvas
/*
canvas oluşturduğumuz sayfanın çerçevesidir. Bu çerçeve sayesinde içerisine resim, yazı gibi görsel öğelerin sayfaya eklenebilmesini sağlıyoruz.
*/
Ekleyeceğimiz starwars logosunun soldan ve üstten hizalanacak şekilde konumunun belirtilmesi gerekmektedir.
canvas.drawBitmap(scaledBitmap, 56F, 40F, paint)
ayrıca sayfaya bir başlık, fonksiyon argümanından gelecek şekilde tanımlamıştık. Başlık yazısının pdf doküman içerisine yazılmasını sağlayalım;
title.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.NORMAL))
title.textSize = 45F
title.setColor(ContextCompat.getColor(context, R.color.purple_700))
characters adlı String tipli ArrayList’den StarWars filmindeki karakterlerin adlarını listeye tek tek eklenmesi için varsayılan bir başlangıç konumu belirtip döngü ile yazıları alt alta belirli bir ölçü ile eklenmesini sağlayalım;
var y = 130F
characters.forEach{
canvas.drawText(it, 450F, y, title)
y += 50F
}
450F x konumudur yani soldan ne kadar boşluk bırakarak yazıya başlayacağımızı belirtiyoruz.
Sayfaya henüz doküman başlığını büyük puntolarda tanımlamadık, şimdi bunu da tanımlayalım;
canvas.drawText(documentTitle, 450F, 80F, title)
artık pdfDoc adlı nesnemizin bitirilmesini sağlayabiliriz. Çünkü başka birşey eklemeyeceğiz.
pdfDoc.finishPage(ourPage)
PDF Dosyamızın External Storage içerisine kaydedilebilmesi için;
var createPdfFile: File = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS).absolutePath, documentName+".pdf")
try {
pdfDoc.writeTo(FileOutputStream(createPdfFile))
Toast.makeText(context, "File downloaded", Toast.LENGTH_LONG).show()
} catch (e: Exception) {
Toast.makeText(context, "An error expected", Toast.LENGTH_LONG).show() //dosyaya erişim sıkıntısı olursa çalışır.
}
artık işlem tamam, pdf dökümanımızı kapatabiliriz 🙂
pdfDoc.close()
işin en civcivli kısımı ise makalenin en başında tanımladığımız external storage’e okuma ve yazma izini hariç kodlarımızın çalışabilmesi için permission tanımlamada. Jetpack Compose’da Accompanist Permissions’ı kullanarak bu işi kolayca halledebilirsiniz. Linki bırakıyorum; https://serifgungor.com/android-jetpack-compose-accompanist-permissions-kullanimi
Yukarıdaki makaleden GeneratePdfFile adlı fonksiyonumuzu çağırma zamanında dosya okuma ve yazma izinini gerçek zamanlı alabildiğimizi öğrendiğimize göre yola koyulalım;
Build.gradle (Module: app) içerisindeki depentencies bloğuna kütüphanemizi dahil edelim;
implementation "com.google.accompanist:accompanist-permissions:0.24.7-alpha"
GeneratePdfFile fonksiyonumuzun içine girip en üst kısma gerekli kodlarımızı dahil edelim;
val externalStoragePermissionsState = rememberMultiplePermissionsState(
permissions = listOf(
android.Manifest.permission.READ_EXTERNAL_STORAGE,
android.Manifest.permission.WRITE_EXTERNAL_STORAGE
)
)
var detectPermissions:Boolean = false
externalStoragePermissionsState.permissions.forEach { permis ->
when(permis.status)
{
is PermissionStatus.Denied -> {
detectPermissions=true
permis.launchPermissionRequest() //izin iste
Toast.makeText(context,permis.permission.toString()+" Permission Denied. Go To App settings for enabling",Toast.LENGTH_LONG).show()
}
}
}
Her bir permission’ı tek tek gezip, status’lerine göre denied olanlarda uyarı veriyoruz. İhtiyaç duyduğumuz herhangi bir izin Granted durumda değil ise (Denied durumda ise) kodların çalışmasını engellemiş olacağız.
if(detectPermissions){
Toast.makeText(context,"Must be open permissions on app settings !",Toast.LENGTH_LONG).show()
}else{
}
else bloğuna metot içerisinde permission tanımlamadan önce yazılmış tüm kodları taşıyoruz.
Çıktıyı dahili depolama alanı içerisinde Documents klasöründe bulabilirsiniz (Eğer Documents klasörünüz yoksa üretiniz).
(İSTEĞE BAĞLI)
yok efendim Documents klasörü yoksa onu da ben kod ile oluşturayım diyorsanız kodlarımız şöyle olacaktır. createPdfFile değişkeninin bulunduğu satırı aşağıdaki gibi güncelleyelim ;
val docsFolder =
File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS).toString())
var isPresent = true
if (!docsFolder.exists()) {
isPresent = docsFolder.mkdir()
}
var createPdfFile: File? = null
if (isPresent) {
createPdfFile= File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS).absolutePath, documentName+".pdf")
}
bunu yaptığımızda şöyle bir bilgi ile karşılaştım internette;
İlk olarak Mart 2019’da açıklandığı gibi, artık varsayılan olarak Android 10+ üzerinde harici depolama veya çıkarılabilir depolama üzerindeki rastgele konumlara erişiminiz yok. Bu, Environment.getExternalStorageDirectory() ve Environment üzerindeki diğer yöntemleri içerir (örneğin, getExternalStoragePublicDirectory().
ve şöyle eklenmiş;
Android 10 ve 11 için, bildirimdeki <application> öğenize android:requestLegacyExternalStorage=”true” ekleyebilirsiniz. Bu, eski depolama modeline geçmenizi sağlar ve mevcut harici depolama kodunuz çalışır.
bu söylenenlere ek olarak Android Güvenlik mekanizması gereği bu sürümlerde ille de Documents gibi bir klasörü ana dizinde üretmek gibi bir niyetiniz devam ediyorsa;
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" />
bu izini de projenize dahil etmeniz gerekiyor. (Google Play politikasına bu permission takılabileceği bilgisini de iletmek isterim.)
Scaffold içerisindeki kodlarımızın son haliyse şöyle;
Scaffold(
topBar = {
TopAppBar(
backgroundColor = turquaseColor,
title = {
Text(
text = "MERHABA",
modifier = Modifier.fillMaxWidth(),
textAlign = TextAlign.Center,
color = Color.White
)
}
)
}
) {
var characters = ArrayList<String>()
characters.add("Darth Vader")
characters.add("Luke Skywalker")
characters.add("Leia Organa")
characters.add("Obi-Wan Kenobi")
characters.add("Chewbacca")
characters.add("Han Solo")
characters.add("Yoda")
GeneratePdfFile(
LocalContext.current,
1120,
792,
"STARWARS CHARACTERS",
characters,
"starwars"
)
}
Üretilen PDF dosyasındaki çıktımız şöyle olacaktır;

Keyifli çalışmalar diliyorum.
Kaynak kodlara github üzerinden erişim sağlayabilirsiniz.
