Hello, everyone! Welcome to another post from my app development journey. This time, I’m sharing the story of all the trials and tribulations I faced while building what I thought was a simple idea: an on-device, interactive info app. What started as a simple concept turned into a multi-day debugging adventure. 😂
1. The 3D Dream and the First Ordeal: flutter_cube
The initial goal was to create a 3D globe that would display information when you tapped on a country. For this, I chose a package called flutter_cube
.
- Problem #1: The 3D model won’t show up!
- Symptom: I followed the example code to display a basic cube, but I was immediately hit with a
The asset does not exist
error. I assumed that an asset included with the package would “just work.” - Failed Attempt: I tried adding the package’s asset path (
packages/flutter_cube/assets/
) directly to mypubspec.yaml
, but that failed with a newunable to find directory
error. - ✅ Solution: The most reliable solution was to manually copy the asset from the package into my own project. I navigated to Flutter’s
.pub-cache
directory, found theflutter_cube
package, copied its entireassets/cube
folder into my project’sassets/
folder, and declared- assets/cube/
in mypubspec.yaml
. Finally, a gray cube appeared on the screen.
- Symptom: I followed the example code to display a basic cube, but I was immediately hit with a
- Problem #2: The Texture API Labyrinth
- Symptom: The journey to apply a texture to that cube was a complete disaster. I tried every API call I could think of:
textureFileName
,object.texture
,object.material.texture
,object.mesh.texture
, andobject.mesh.material.texture
. Every single one failed. - ✅ Solution (or, giving up): After a long battle, I decided to stop fighting with the
flutter_cube
package and look for a more modern, easier-to-use alternative. Sometimes, switching your tools is the smartest choice.
- Symptom: The journey to apply a texture to that cube was a complete disaster. I tried every API call I could think of:
2. A New Hope and Another Wall: model_viewer_plus
My next choice was model_viewer_plus
, a package based on Google’s 3D viewer technology.
- Problem #1:
The method 'ModelViewer' isn't defined
- ✅ Solution: This was a classic rookie mistake! I forgot to add the
import 'package:model_viewer_plus/model_viewer_plus.dart';
statement at the top of my file. (Happens to the best of us, right? 😉)
- ✅ Solution: This was a classic rookie mistake! I forgot to add the
- Problem #2: Hitting a Conceptual Dead End
- Symptom: I successfully displayed a beautiful, interactive
.glb
model of the Earth. But when I tried to implement the core feature—tapping on a specific country—I realized there was no way to do it. - ✅ Solution (A strategic pivot): I concluded that
model_viewer_plus
is, as the name implies, more of a “viewer” than a full 3D engine. It’s fantastic for showcasing models but not for complex interactions. I had to give up on the 3D dream for now and pivot the plan to a 2D interactive map.
- Symptom: I successfully displayed a beautiful, interactive
3. The Final Destination: syncfusion_flutter_maps
I finally landed on syncfusion_flutter_maps
, which has strong support for offline GeoJSON rendering and interactivity. This is where the last of my “struggles” began.
- Problem #1: The map isn’t drawing! (The Black Screen of Death)
- Symptom: The app compiled and ran without errors, but all I saw was a black screen.
- Cause: The data I was trying to use for my
world_map.json
file was a plain JSON with country info, not a GeoJSON file with border coordinates. The package had no shape data to draw. - ✅ Solution: Using a correctly formatted GeoJSON file fixed the issue. Understanding the exact data format your tools expect is critical.
- Problem #2: The Map Only Appears on Hot Reload!
- Symptom: The app would show a blank screen on the initial launch, but the map would magically appear after a single hot reload.
- Cause: This was a classic race condition. The Flutter app was trying to build the
SfMaps
widget before theworld_map.json
asset was fully loaded and ready on the native side. Hot reload worked because the asset was already available after the initial launch. - ✅ Solution: Using a
FutureBuilder
was the right idea, but my first implementation was flawed. The final, correct solution was to use theFutureBuilder
to first load the raw JSON string from the asset bundle into memory. Then, once the data is ready, the builder function creates the map source from that in-memory data usingMapShapeSource.memory
. This guarantees the data is fully loaded before the widget tries to render it.
Conclusion & Final Code
After a long journey, we finally have the foundation for our on-device, interactive 2D map app. This experience was a great reminder that sometimes the most important part of development is choosing the right tool for the job and understanding the low-level details of how your app loads data.
Here is the final, stable code that works on the first try:
// lib/main.dart (Final, working code)
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:syncfusion_flutter_maps/maps.dart';
// ... (MyApp class)
class GlobeScreen extends StatefulWidget {
const GlobeScreen({super.key});
@override
State<GlobeScreen> createState() => _GlobeScreenState();
}
class _GlobeScreenState extends State<GlobeScreen> {
// The Future now waits for the String content of the file
late Future<String> _geoJsonData;
@override
void initState() {
super.initState();
// In initState, we just start the Future that reads the asset file
_geoJsonData = rootBundle.loadString('assets/data/world_map.json');
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Info Globe'),
),
// The FutureBuilder now waits for String data
body: FutureBuilder<String>(
future: _geoJsonData,
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
}
if (snapshot.hasError) {
return Center(child: Text('Error loading map: ${snapshot.error}'));
}
if (snapshot.hasData) {
// Once the data is loaded, create the map source from memory
final MapShapeSource mapSource = MapShapeSource.memory(
// Convert the string data to bytes
utf8.encode(snapshot.data!),
shapeDataField: 'name',
);
return SfMaps(
layers: <MapLayer>[
MapShapeLayer(
source: mapSource, // Use the source created from memory
color: Colors.grey[350],
strokeColor: Colors.white,
strokeWidth: 0.5,
// We'll add selectionSettings and onSelectionChanged here later
),
],
);
}
return const Center(child: Text('Loading map...'));
},
),
);
}
}
Reference
https://heavenly.tistory.com/entry/Flutter-인터랙티브-지도-앱-만들기-삽질과-해결의-대서사시