- Install
- Download SDK
- Add to your project
- Add to project
- The View (THNWidget Container)
- The Data (THNDataManager)
- Add to the MainView
- Configure the SDK
- What are THN Pages and Why are important?
- How to specify the pages in the SDK
- Update other THN Data
- Google analytics
- isUserLoggedIn
- DataLayer
- Capture promocodes
Download the latest Android agent (AAR) from our Android SDK release notes.
-
Download the AAR file
- Navigate to the THN Native SDK releases
- Download the latest
thn-native-sdk-x.x.x.aarfile - Save it to a location you can easily access
-
Create libs directory
mkdir -p app/libs
-
Copy AAR to your project
- Copy the downloaded
thn-native-sdk-x.x.x.aarfile to yourapp/libs/directory - Rename it to
thnnative.aarfor simplicity (optional)
- Copy the downloaded
-
Configure Gradle dependencies Add to your
app/build.gradlefile:
dependencies {
implementation files("libs/thnnative.aar")
}- Verify installation
Your project structure should look like this:
app/
├── libs/
│ └── thn-native-sdk.aar
├── build.gradle
└── src/
└── main/
├── AndroidManifest.xml
└── java/kotlin/
After adding the AAR file, sync your project and verify the integration:
- Sync Project: Click "Sync Now" in Android Studio
- Clean and Rebuild:
./gradlew clean ./gradlew build
- Verify imports: You should be able to import THN classes:
import com.thehotelsnetwork.thnnativesdk.*
The THN Native SDK uses a simple WebView-based approach. You can integrate it directly into your layouts and update data as needed.
### Add to Layout
Add the THNWidgetLoader directly to your layout XML:
`activity_main.xml`
```xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- Your main content -->
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_graph" />
<!-- THN Widget Loader -->
<com.thehotelsnetwork.thnnativesdk.THNWidgetLoader
android:id="@+id/thn_widget_loader"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
Configure the widget in your Activity:
MainActivity.kt
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.thehotelsnetwork.thnnativesdk.THNWidgetLoader
import com.thehotelsnetwork.thnnativesdk.THNData
import com.thehotelsnetwork.thnnativesdk.THNPageName
class MainActivity : AppCompatActivity() {
private lateinit var thnWidgetLoader: THNWidgetLoader
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Initialize THN Widget Loader
thnWidgetLoader = findViewById(R.id.thn_widget_loader)
thnWidgetLoader.configure(
showDebug = true,
data = THNData(
propertyId = "1159354",
pageName = THNPageName.INDEX,
languageCode = "EN",
currencyCode = "USD",
),
onPromocodeReceived = { promocodes ->
// Handle promocodes received
promocodes.forEach { promocode ->
println("Debug: Promocode received: $promocode")
// Apply promocode to your booking flow
}
}
)
// Load initial data
updateTHNData()
}
private fun updateTHNData() {
val data = THNData(
propertyId = "1234567", // THN Property ID
pageName = THNPageName.INDEX,
languageCode = "EN",
currencyCode = "USD"
)
thnWidgetLoader.updateData(data)
}
}With THNPages we will share the actual view type between the native app and the SDK to enable identify which widget should show. THN Personalization widgets are linked to a property and a THNPage. It's necessary to indicate to the SDK which page of the native application we are on so it can evaluate which widgets need to be displayed.
Not all room types are mandatory, define and use the ones your application needs.
There are different types of pages according to their meaning for the SDK:
These parameters are mutually exclusive and account ref takes priority over property id.
Index This is usually the initial page of the application or that of a specific hotel if your application includes all those in the chain.
- propertyId - The THN property ID that identifies the hotel
- accountRef - The THN account REF that identifies a chain
These parameters are mutually exclusive and account ref takes priority over property id.
HomeFragment.kt
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.thehotelsnetwork.thnnativesdk.*
class HomeFragment : Fragment() {
private lateinit var thnWidgetLoader: THNWidgetLoader
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_home, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
thnWidgetLoader = view.findViewById(R.id.thn_widget_loader)
updateTHNData()
}
private fun updateTHNData() {
val data = THNData(
propertyId = "1234567", // THN property ID
// or set accountRef: ""
pageName = THNPageName.INDEX,
languageCode = "EN",
currencyCode = "USD"
)
thnWidgetLoader.updateData(data)
}
}The users availability search results page where appear the list of the rooms with the rates.
- Search - Booking search results
- Results - Search Results resume
RoomsFragment.kt
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.thehotelsnetwork.thnnativesdk.*
class RoomsFragment : Fragment() {
private lateinit var thnWidgetLoader: THNWidgetLoader
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_rooms, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
thnWidgetLoader = view.findViewById(R.id.thn_widget_loader)
updateTHNData()
}
private fun updateTHNData() {
val data = THNData(
propertyId = "1234567",
pageName = THNPageName.ROOMS_AND_RATES,
languageCode = "EN",
currencyCode = "USD",
// *Required* Search results data
search = THNSearch(
checkIn = "2025-10-01",
checkOut = "2025-10-02",
promoCode = null,
adults = 2,
children = 1,
rooms = listOf(
THNRoom(
adults = 2,
name = "Double room with extra bed",
type = THNRoomKind.STANDARD,
children = 1,
prices = listOf(
THNPriceOption(
name = "Rate name",
totalNight = 100.0,
breakfast = true,
refundable = true
)
// Include other prices ...
)
)
// Include other Rooms ...
)
),
// *Required* Results resume data
results = THNResults(
availability = true,
lowestPriceWithTaxes = 121.0 // or lowestPriceWithoutTaxes = 100.0,
)
// Include other updated data ...
)
thnWidgetLoader.updateData(data)
}
}The view where the user fills in the guest data for the reservation.
UserRegistrationFragment.kt
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.thehotelsnetwork.thnnativesdk.*
class UserRegistrationFragment : Fragment() {
private lateinit var thnWidgetLoader: THNWidgetLoader
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_user_registration, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
thnWidgetLoader = view.findViewById(R.id.thn_widget_loader)
updateTHNData()
}
private fun updateTHNData() {
val data = THNData(
propertyId = "1234567",
pageName = THNPageName.USER_REGISTER_PAGE,
languageCode = "EN",
currencyCode = "USD"
// Include other updated data ...
)
thnWidgetLoader.updateData(data)
}
}The view where the user can see the message that their reservation has been confirmed.
- BookingId - The booking confirmation identifier (LOC, BID, etc)
BookingConfirmedFragment.kt
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.thehotelsnetwork.thnnativesdk.*
class BookingConfirmedFragment : Fragment() {
private lateinit var thnWidgetLoader: THNWidgetLoader
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_booking_confirmed, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
thnWidgetLoader = view.findViewById(R.id.thn_widget_loader)
updateTHNData()
}
private fun updateTHNData() {
val data = THNData(
propertyId = "1234567",
pageName = THNPageName.BOOKING_CONFIRMED,
languageCode = "EN",
currencyCode = "USD",
bookingId = "ABC-123" // *Required* The booking confirmation id
// Include other updated data ...
)
thnWidgetLoader.updateData(data)
}
}Register events within Google Analytics contain user interactions or activities with specific elements on a web page that are tracked and measured for analytical purposes. In this property you can specify your Google Analytics Tag ID and the SDK will load it.
If the user is logged in the native app we should specify this property. There may be messages that will only appear if the user is logged in.
Here we can specify other useful data that we can use when setting the targeting rules for messages in THN Personalization.
You can include additional data in your THNData object:
val data = THNData(
propertyId = "1234567",
pageName = THNPageName.INDEX,
languageCode = "EN",
currencyCode = "USD",
googleAnalyticsIds = listOf("GA-TRACKING-ID"), // Google analytics IDs
isUserLogged = false, // Check if logged in the app
dataLayer = listOf(
mapOf("userInHouse" to "true"),
mapOf("userRegistered" to "false")
// ... add other data needed to target your users
)
)Some of the THN Personalization messages offer deals and discounts to the user in the form of promocodes. When the SDK detects that a promocode is being applied, THNWidgetLoader will emit an event with that promocode so you can apply the necessary actions in the native application.
For testing and development, you can enable debug mode:
// Enable debug mode to see widget boundaries and console logs
thnWidgetLoader.setDebugMode(true)Set up the promocode callback to capture and handle promocodes:
class MainActivity : AppCompatActivity() {
private lateinit var thnWidgetLoader: THNWidgetLoader
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
thnWidgetLoader = findViewById(R.id.thn_widget_loader)
// -------------------------
// Promocodes Event handle
// -------------------------
thnWidgetLoader.onPromocodeReceived = { promocodes ->
// Here we can capture the promocode events and apply them
// inside the Native app.
// Multiple promocodes can be received.
promocodes.forEach { promocodeData ->
val promocode = promocodeData["code"] as? String
println("Debug: Promocode received: $promocode")
// Example: Apply promocode to search
promocode?.let { applyPromocodeToSearch(it) }
}
}
// Load initial data
updateTHNData()
}
private fun applyPromocodeToSearch(promocode: String) {
// Get current data and update with new promocode
val currentData = getCurrentTHNData() // Your method to get current data
val updatedSearch = currentData.search?.copy(promoCode = promocode)
val updatedData = currentData.copy(search = updatedSearch)
// Update the widget with the new data
thnWidgetLoader.updateData(updatedData)
}
private fun getCurrentTHNData(): THNData {
// Return your current THNData state
// This could be stored in a ViewModel or other state management solution
return THNData(
propertyId = "1234567",
pageName = THNPageName.ROOMS_AND_RATES,
languageCode = "EN",
currencyCode = "USD"
)
}
}