Develop House-Ads API With Laravel For Mobile App/Game
You’re probably familiar with house ads (or cross-promotion ads) in mobile games. Famous game publishers like Vodoo use house ads extensively to cross-promo their products. While you can put the house ads locally and pre-built them in the apps, having a server to serve the API and assets can give you live control of the ads. You can also use this as app event notices, to gives dynamic information and interaction with your users. This article will walk you through how to create the API calls with Laravel step-by-step.
TLDR; You can use review the code or simply install my Laravel House Ads package.
Overview
We are aiming to design a generic API, that allows clients to serve the house ads in different formats with the corresponding analytic data. Two most use cases are:
- UI Box
- Interstitial Popup
UI Box
The most used house ads type that is used by most game publishers, usually a GIF/video playing in the menu and do not interfere with the users. For analytics, we may be interested in how many times an ad shows and how many times it has clicked.
Interstitial Popup
A popup that usually shows in app-launch. It may contain a cancel button to go back to the app, and a confirm button to link to the cross-promo app or webpage. The interstitial popup can also be used as an in-app notification for anything you want to inform the users, such as version-upgrade, offline events, etc. For analytics, we are interested in how many times an ad shows, cancel and confirm button are clicked.
Database migrations
By using Laravel’s database migrations, the database setup is pretty simple, we only need 1 database:
House Ads Table
php artisan make:migration create_house_ads_table
/database/migrations/{datetime}_create_house_ads_table.php
:
<?php
use IlluminateDatabaseSchemaBlueprint;
use IlluminateDatabaseMigrationsMigration;
use IlluminateSupportFacadesSchema;
class CreateHouseAdsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('house_ads', function(Blueprint $table) {
$table->id();
$table->integer('game_id')->unsigned();
$table->string('media_portrait', 128)->nullable();
$table->string('media_landscape', 128)->nullable();
$table->boolean('open_url')->default(true);
$table->string('url_ios', 256)->nullable();
$table->string('url_android', 256)->nullable();
$table->tinyInteger('repeat_count')->unsigned()->default('1');
$table->tinyInteger('priority')->unsigned()->default('1');
$table->date('start_at');
$table->date('end_at');
$table->mediumInteger('shown_count')->unsigned()->default('0');
$table->mediumInteger('confirmed_count')->unsigned()->default('0');
$table->mediumInteger('cancelled_count')->unsigned()->default('0');
$table->timestamps();
$table->foreign('game_id')->references('id')->on('games')->onDelete('cascade');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('house_ads');
}
}
- game_id – which game/app this house ad redirect to. This is used in client app to distingish and not showing its own house ad.
- media_portrait – the portrait image/video filename. The media should be uploaded and stored in
<server root>/media
folder. You can also use video and implement the playing function in client app. - media_landscape – the landscape image/video filename.
- open_url – should the house ad open and URL, otherwise the client should simply show the image/video. This is mostly used for showing an event image, cutscene video, etc.
- url_ios – the URL to open in iOS devices.
- url_android – the URL to open in Android devices.
- repeat_count – how many app launches to wait to show this ad again, for interstitial popup only.
- priority – the highest priority ad get shown in one app launch.
- start_at – the date that this house ad start, used this to schedule future house ads.
- end_at – the date that this house ad end, used this to end promotion with a given period.
- shown_count – the shown count, used for analytics only.
- confirmed_count – the confirmed count (successful redirect), used for analytics only.
- cancelled_count – The cancelled count (failed redirect), used for analytics only.
Note that we use the Game Essentials package here for basic game database setup, which will not be covered in this article.
Models
Then, we simply map the database table as its own Laravel Eloquent model. Again, we only need one model here:
House Ad model
php artisan make:model HouseAd
/app/Models/HouseAd.php
:
<?php
namespace FuricHouseAdsModels;
use IlluminateDatabaseEloquentModel;
use FuricGameEssentialsModelsGame;
class HouseAd extends Model
{
protected $guarded = [];
protected $hidden = ['game_id', 'media_portrait', 'media_landscape', 'confirmed_count', 'cancelled_count', 'start_at', 'end_at', 'created_at', 'updated_at'];
protected $appends = ['url_media_portrait', 'url_media_landscape', 'game'];
public function getUrlMediaPortraitAttribute()
{
return url('/media/'.$this->media_portrait);
}
public function getUrlMediaLandscapeAttribute()
{
return url('/media/'.$this->media_landscape);
}
public function game()
{
return $this->belongsTo(Game::class);
}
public function getGameAttribute()
{
return $this->game()->first();
}
}
HouseAd
has belongsTo
relationship to a game
model, and contains the addictive media URL fields.
Routing
We simply have the API route in Laravel Routing. You may want to add Web route so for the admin console to manage the house ads, but again not covered here.
API route
In /routes/api.php
, add:
<?php
use IlluminateSupportFacadesRoute;
use FuricHouseAdsHttpControllersHouseAdController;
Route::prefix('api')->group(function() {
Route::resource('house-ads', HouseAdController::class)->only([
'index', 'show', 'update'
]);
});
This creates API routes as {api-url}/house-ads
and {api-url}/house-ads/{id}
for your client app to checking the current house ads and update specific one for analytics.
Controllers
Finally, we will have one Laravel Controller:
HouseAdController
php artisan make:controller HouseAdController
/Http/Controllers/HouseAdController.php
:
<?php
namespace FuricHouseAdsHttpControllers;
use FuricHouseAdsModelsHouseAd;
use AppHttpControllersController;
use IlluminateHttpRequest;
use Validator;
class HouseAdController extends Controller
{
/**
* Display a listing of the house ad resource.
*
* @return IlluminateHttpResponse
*/
public function index()
{
return response(HouseAd::whereDate('start_at', '<=', date('Y-m-d'))->whereDate('end_at', '>=', date('Y-m-d'))->orderBy('priority', 'desc')->get(), 200);
}
/**
* Display the specified house ad resource.
*
* @param int $id
* @return IlluminateHttpResponse
*/
public function show($id)
{
try {
return response(HouseAd::findOrFail($id), 200);
} catch (IlluminateDatabaseEloquentModelNotFoundException $e) {
return response([
'error' => 'No house ad found.'
], 400);
}
}
/**
* Update the specified house ad resource in storage.
*
* @param Request $request
* @param int $id
* @return IlluminateHttpResponse
*/
public function update(Request $request, $id)
{
$validator = Validator::make($request->all(), [
'confirmed' => 'sometimes|required|numeric',
'shown' => 'sometimes|required|numeric',
]);
if ($validator->fails()) {
return response([
'error' => 'Key "confirmed" required.'
], 400);
}
try {
$houseAd = HouseAd::findOrFail($id);
if ($request->has('confirmed')) {
if ($request->confirmed == '1') {
$houseAd->confirmed_count++;
} else {
$houseAd->cancelled_count++;
}
}
if ($request->has('shown')) {
$houseAd->shown_count++;
}
$houseAd->save();
return response($houseAd, 200);
} catch (IlluminateDatabaseEloquentModelNotFoundException $e) {
return response([
'error' => 'No house ad found.'
], 400);
}
}
}
In HouseAdConstroller
, we only index()
, show()
and update()
.
index()
– listing the house ads which current datetime is within theirstart_at
andend_at
.show()
– showing a specific house ad, for debug only.update()
– updating the analytics count of a given house ad.
Conclusion
That’s it! We’ve set up a simple API call to list and update house ads. Now simply add the database entries and their corresponding media file in <server root>/media
folder, it should be ready to go!
Again, you can read all code in the GitHub repo or simply install the package using composer, while the project is still pretty simple and there’s few TODOs.
Oh, that’s the server back-end part, I will write another post on how to call the API and handle the response in Unity later. 😀
Lastly, leave a comment if you got any questions and wish this article and the package may help you.