Nessa terceira parte do nosso tutorial de Web Scraping com Jsoup no Android Studio em Kotlin vamos envio de dados para outra Activity quando clicarmos em um item do recyclerview e também a carregar mais informações da noticia selecionada de outra pagina web.
O que você vai aprender:
- Enviar dados via onClickListener para outra Activity
- Como fazer filtragem de tags com Jsoup
- Como criar Menu e chama-lo na Activity
- Como ativar botão Home As Up
- Usar provedor de conteúdo FileProvider para compartilhar imagem com outros apps (como instagram e whatsapp)
Criar a Activity Empty “DetailsNewsActivity“
Agora iremos implementar o envio de dados via onClickListener no NewsAdapter na função “onBindViewHolder” que criamos na parte 2 desse tutorial:
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (holder is NewsViewHolder){
holder.bindView(news[position]!!)
} else if (holder is LoadViewHolder){
holder.bindView()
}
//sending data
val newsItem = news[position]
holder.itemView.setOnClickListener {
val intent = Intent(activity, DetailsNewsActivity::class.java)
intent.putExtra("IMAGE", newsItem!!.image)
intent.putExtra("TITLE", newsItem.title)
intent.putExtra("DETAILS", newsItem.details)
activity.startActivity(intent)
}
}
Crie o drawable “background_shape_details” para personalizarmos o fundo do nosso layout:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid
android:color="@color/colorAccent"/>
<corners
android:topLeftRadius="32dp"
android:topRightRadius="32dp"/>
</shape>
Agora podemos começar a editar o “layout_details_news” do DeitalsNewsActivity:
<?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:background="@color/colorPrimaryDark"
tools:context=".DetailsNewsActivity">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:background="@drawable/background_shape_details">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/txt_title_detail"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/TextAppearance.AppCompat.Headline"
android:layout_margin="12dp"
android:padding="4dp"
android:text="Title"
android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintEnd_toStartOf="parent"
app:layout_constraintStart_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<androidx.cardview.widget.CardView
android:id="@+id/cardView"
android:layout_width="match_parent"
android:layout_height="250dp"
style="@style/CardView.Light"
android:layout_marginTop="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/txt_title_detail">
<ImageView
android:id="@+id/image_detail"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="@drawable/gradient_card"/>
</androidx.cardview.widget.CardView>
<TextView
android:id="@+id/txt_detail"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="12dp"
android:layout_marginBottom="16dp"
android:hint="Loading..."
android:padding="4dp"
android:textSize="16sp"
app:layout_constraintEnd_toStartOf="@id/cardView"
app:layout_constraintStart_toEndOf="@id/cardView"
app:layout_constraintTop_toBottomOf="@id/cardView"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
Crie a interface “ILoadDetails” que será responsável por retornar os dados vindos da classe AsyncTask que criaremos mais a frente:
interface ILoadDetails {
fun getDetails(details: ArrayList<String>)
}
Agora crie a classe AsyncTask “LoadDetailsNews” que receberá o endereço da página que tem o texto completo da notícia onde através do Jsoup filtraremos as tags corretas para pegar apenas o texto e retornaremos o resultado pela interface “ILoadDetails”:
Obs.: Sendo que algumas notícias tem listas (tags “li”) em seu texto (disposto em tags “p”) e outras não, precisamos filtra-las de uma forma que sirva para as duas situações! O que pode ser observado no código abaixo. Mas caso haja dúvida você pode ver com mais detalhes no vídeo tutorial em nossa canal Dev Mundi.
import android.os.AsyncTask
import androidx.appcompat.app.AppCompatActivity
import org.jsoup.Jsoup
import org.jsoup.nodes.Document
import org.jsoup.select.Elements
import java.io.IOException
class LoadDetailsNews(activity: AppCompatActivity, var urlDetails: String):
AsyncTask<Void, Void, ArrayList<String>>() {
private var details: ArrayList<String>? = ArrayList()
private var loader = activity as ILoadDetails
override fun doInBackground(vararg params: Void?): ArrayList<String> {
try{
val baseUrl = "https://www.saude.gov.br"
val url = baseUrl+urlDetails
val doc: Document = Jsoup.connect(url).get()
val div: Elements = doc.select("div.item-pagenoticias")
div.select("div").remove()
//get only the text inside the tags "p" and "li"
val textComplete = div.select("p, ul > li")
for (element in textComplete){
if (element.text() != "")
details!!.add(element.text())
}
}catch (e: IOException){
e.printStackTrace()
}
return details!!
}
override fun onPostExecute(result: ArrayList<String>?) {
loader.getDetails(result!!)
}
}
Crie o Vector Asset “ic_share” para o icone do nosso menu:

E agora click com o botão direito na pasta res e vá em New -> Android Resource File:

E crie o “menu_details_news” :

Agora crie o item “Share” no “menu_details_news”:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/btn_share"
android:icon="@drawable/ic_share"
app:showAsAction="always"
android:title="Share" />
</menu>
Precisamos criar também o arquivo com o caminho onde será buscado a image compartilhado no provedor de conteúdo, então crie o xml “file_provider_path.xml”:

Edite o caminho dentro do “file_provider_path”:
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="image-share" path="."/>
</paths>
E finalmente crie o provider no AndroidManifest:
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_provider_path"/>
</provider>
Assim ficará todo o código do AndroidManifest:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="your.package.name">
<!-- Esse nome é fictício. Don't change the package's name in your project -->
<uses-permission android:name="android.permission.INTERNET" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".DetailsNewsActivity"
android:label=""/>
<activity
android:name=".SplashActivity"
android:theme="@style/Splash">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".MainActivity"
android:label="IS IT FAKE NEWS?" />
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_provider_path"/>
</provider>
</application>
</manifest>
Agora só falta implementar o códigodo DetailsNewActivity onde:
- Receberemos os dados enviados via onClickListener do item do recyclerview
- Recebermos os dados vindos da classe LoadDetailsNews através da interface ILoadDetails com a função “getDetails”
- Implementar o botão Home As Up no ActoBar
- Criaremos a função “getLocalBitmapUri” para usar o provedor de conteúdo Fileprovider para criar um caminho de “resgate” da image
- Criaremos a função “shareNewsImage” pegar a image em formato Bitmap direto do Picasso e para enviar com a ajuda da função “getLocalBitmapUri” a image para outro app
- Implementaremos no menu_details_news na nossa Activity e as eventos no botão share para compartilhar a imagem e no botão home do ActionBar para fechar a Activity.
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.drawable.Drawable
import android.net.Uri
import android.os.AsyncTask
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.os.Environment
import android.view.Menu
import android.view.MenuItem
import androidx.core.content.FileProvider.getUriForFile
import com.squareup.picasso.Picasso
import com.squareup.picasso.Target
import kotlinx.android.synthetic.main.activity_details_news.*
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.lang.Exception
class DetailsNewsActivity : AppCompatActivity(), ILoadDetails {
private var urlImage: String? = null
private var urlDetails: String? = null
private var loadDetailsNews: AsyncTask<Void, Void, ArrayList<String>>? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_details_news)
urlImage = intent.getStringExtra("IMAGE")
urlDetails = intent.getStringExtra("DETAILS")
txt_title_detail!!.text = intent.getStringExtra("TITLE")
Picasso.get().load(urlImage).into(image_detail)
loadDetailsNews = LoadDetailsNews(this, urlDetails!!)
loadDetailsNews!!.execute()
supportActionBar!!.setDisplayHomeAsUpEnabled(true)
}
fun shareNewsImage(){
Picasso.get().load(urlImage).into(object : Target{
override fun onPrepareLoad(placeHolderDrawable: Drawable?) { }
override fun onBitmapFailed(e: Exception?, errorDrawable: Drawable?) { }
override fun onBitmapLoaded(bitmap: Bitmap?, from: Picasso.LoadedFrom?) {
val intent = Intent(Intent.ACTION_SEND)
intent.setType("image/*")
intent.putExtra(Intent.EXTRA_STREAM, getLocalBitmapUri(bitmap!!))
startActivity(Intent.createChooser(intent, "Share"))
}
private fun getLocalBitmapUri(bitmap: Bitmap): Uri? {
var bitmapUri: Uri? = null
try {
val file = File(getExternalFilesDir(Environment.DIRECTORY_PICTURES),
"shareimage" + ".png")
val out =FileOutputStream(file)
bitmap.compress(Bitmap.CompressFormat.PNG, 90, out)
out.close()
bitmapUri = getUriForFile(applicationContext,
applicationContext.packageName + ".fileprovider",
file)
}catch (e: IOException){
e.printStackTrace()
}
return bitmapUri
}
})
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.menu_activity_details_news, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
val id:Int = item.itemId
when(id){
android.R.id.home -> finish()
R.id.btn_share -> shareNewsImage()
}
return super.onOptionsItemSelected(item)
}
override fun getDetails(details: ArrayList<String>) {
for (index in 0..details.size-1){
if (index == details.size-1){
txt_detail.append("\n" + details[index] + "\n")
}else{
txt_detail.append(details[index] + "\n\n")
}
}
}
}
Já conseguimos mandar os dados para o “DetailsNewsActivity” através de um item do Recyclerview, carregar o texto com os detalhes da noticia em tempo real e o mais legal: Podemos compartilhar a verdade com apenas o click de um botão!
Nosso app anti-fake news está pronto! Espero que tenha te ajudado tudo que foi apresentado aqui!
Caso fique dúvida nos passos desse tutorial você pode assistir ao vídeo do canal Dev Mundi abaixo que contem mais detalhes!