I have a leaderboard screen that displays the top 10 players. I’m encountering issues with handling their images efficiently. Currently, the server sends player images to the client in base64 format. The client decodes these images to Uint8List
and displays them using Image.memory
. While this method works, it negatively impacts performance, memory usage, and server resources. I attempted to use cached_network_image
, but it results in numerous requests to the server, causing server overload. Is there a more efficient approach for retrieving and displaying these images?
Server side:
async function getLeaderboards(limit, offset) {
try {
const result = await pool.query('SELECT img, username, coins, gems FROM users ORDER BY coins DESC LIMIT ? OFFSET ?', [limit, offset]);
// Modify the result to set img to null if it doesn't exist
for (const user of result) {
if (!isValidImage(user.img)) {
user.img = null;
}
}
return result;
} catch (err) {
console.log(err);
throw err; // Propagate the error
}
}
router.post('/leaderboards', checkTokenMiddleware, leaderboardsValidator, async (req, res) => {
try {
const { limit, page } = req.body;
const offset = (page - 1) * limit;
const leaderboards = await getOrSetCache(`leaders?p=${page}`, async () => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
res.status(400).json({ errors: errors.array() });
return;
}
return await getLeaderboards(limit, offset);
});
// Prepare leaderboards data with base64-encoded images
const leaderboardsWithImages = [];
for (const user of leaderboards) {
if (user.img !== null) {
const filePath = path.join(__dirname, '..', '..', 'uploads', user.img);
const fileData = fs.readFileSync(filePath, { encoding: 'base64' });
const base64Image = `data:image/jpg;base64,${fileData}`;
leaderboardsWithImages.push({ ...user, img: base64Image });
} else {
leaderboardsWithImages.push({ ...user });
}
}
// Send the leaderboards data with images embedded as base64 strings
res.status(200).json(leaderboardsWithImages);
console.log(leaderboardsWithImages)
} catch (err) {
handleDatabaseError(res, err);
}
});
so the server response is a list of 10 maps. something like this:
[{
img: 'data:image/jpg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL....190834 more characters,
username: 'hala',
coins: 110,
gems: 5
}]
Flutter:
Future<List<dynamic>?> fetchAPIData() async {
try {
final response = await serv.postRequest(
APIConstants.kLeaderboards,
showLoading: false,
{'page': page, 'limit': limit},
);
if (response.isEmpty) {
hasMorePages = false;
update();
}
for (var item in response) {
leaderboardData!.add(Leaderboards.fromMap(item));
}
page++;
update();
return leaderboardData;
} catch (e) {
logger.e("Error fetching leaderboard API data: $e");
return null;
}
}
Uint8List base64ToUint8List(String base64String) {
return base64.decode(base64String.split(',').last);
}
// i display this widget in a listview.builder
Image.memory( ctr.base64ToUint8List(imgFile),fit: BoxFit.cover)