Search Category¶
Setup¶
Prerequisites¶
Build and Run¶
Go to the search_category
directory within the Flutter examples directory.
Note - the gem_kit
directory containing the Maps SDK for Flutter
should be in the plugins
directory of the example, e.g.
example_pathname/plugins/gem_kit
- see the environment setup guide above.
Run: flutter pub get
Configure the native parts:
First, verify that the ANDROID_SDK_ROOT
environment variable
is set to the root path of your android SDK.
In android/build.gradle
add the maven
block as shown,
within the allprojects
block, for both debug and release builds:
1 allprojects {
2 repositories {
3 google()
4 mavenCentral()
5 maven {
6 url "${rootDir}/../plugins/gem_kit/android/build"
7 }
8 }
9 }
in android/app/build.gradle
within the android
block, in the defaultConfig
block,
the android SDK version minSdk
must be set as shown below.
Additionally, for release builds, in android/app/build.gradle
,
within the android
block, add the buildTypes
block as shown:
Replace example_pathname
with the actual project pathname
1android {
2 defaultConfig {
3 applicationId "com.magiclane.gem_kit.examples.example_pathname"
4 minSdk 21
5 targetSdk flutter.targetSdk
6 versionCode flutterVersionCode.toInteger()
7 versionName flutterVersionName
8 }
9 buildTypes {
10 release {
11 minifyEnabled false
12 shrinkResources false
13
14 // TODO: Add your own signing config for the release build.
15 // Signing with the debug keys for now, so `flutter run --release` works.
16 signingConfig signingConfigs.debug
17 }
18 }
19}
Then run the project:
flutter run --debug
orflutter run --release
How It Works¶
This example demonstrates how to enable search functionality for landmarks using the ``gem_kit``package.
Import Necessary Packages¶
First, import the required packages in your Dart code.
1import 'package:gem_kit/core.dart';
2import 'package:gem_kit/map.dart';
3
4import 'search_page.dart';
5
6import 'package:flutter/material.dart';
Initialize GemKit¶
In the main
function, initialize GemKit with your project API token.
1void main() async {
2 const projectApiToken = String.fromEnvironment('GEM_TOKEN');
3 await GemKit.initialize(appAuthorization: projectApiToken);
4 runApp(const MyApp());
5}
Build the Main Application¶
Define the main application widget, MyApp
.
1class MyApp extends StatelessWidget {
2 const MyApp({super.key});
3
4 @override
5 Widget build(BuildContext context) {
6 return const MaterialApp(
7 debugShowCheckedModeBanner: false,
8 title: 'Search Category',
9 home: MyHomePage(),
10 );
11 }
12}
Handle Map and Search Functionality¶
Create the stateful widget, MyHomePage
, which will handle the map and search functionality.
1class MyHomePage extends StatefulWidget {
2 const MyHomePage({super.key});
3
4 @override
5 State<MyHomePage> createState() => _MyHomePageState();
6}
Define State Variables and Methods¶
Within _MyHomePageState
, define the necessary state variables and methods to manage the map and perform searches.
1class _MyHomePageState extends State<MyHomePage> {
2 late GemMapController _mapController;
3
4 @override
5 void dispose() {
6 GemKit.release();
7 super.dispose();
8 }
9
10 @override
11 Widget build(BuildContext context) {
12 return Scaffold(
13 appBar: AppBar(
14 backgroundColor: Colors.deepPurple[900],
15 title: const Text("Search Category", style: TextStyle(color: Colors.white)),
16 actions: [
17 IconButton(
18 onPressed: () => _onSearchButtonPressed(context),
19 icon: const Icon(Icons.search, color: Colors.white),
20 ),
21 ],
22 ),
23 body: GemMap(
24 onMapCreated: _onMapCreated,
25 ),
26 );
27 }
28
29 void _onMapCreated(GemMapController controller) {
30 _mapController = controller;
31 }
32
33 // Custom method for navigating to search screen
34 void _onSearchButtonPressed(BuildContext context) async {
35 // Taking the coordinates at the center of the screen as reference coordinates for search.
36 final x = MediaQuery.of(context).size.width / 2;
37 final y = MediaQuery.of(context).size.height / 2;
38 final mapCoords = _mapController.transformScreenToWgs(XyType(x: x.toInt(), y: y.toInt()));
39
40 // Navigating to search screen. The result will be the selected search result (Landmark)
41 final result = await Navigator.of(context).push(MaterialPageRoute<dynamic>(
42 builder: (context) => SearchPage(
43 controller: _mapController,
44 coordinates: mapCoords!,
45 ),
46 ));
47
48 if (result is Landmark) {
49 // Activating the highlight
50 _mapController.activateHighlight([result], renderSettings: HighlightRenderSettings());
51 // Centering the map on the desired coordinates
52 _mapController.centerOnCoordinates(result.coordinates);
53 }
54 }
55}
Define Search Functionality¶
Implement the SearchPage
widget that allows users to search for landmarks.
1class SearchPage extends StatefulWidget {
2 final GemMapController controller;
3 final Coordinates coordinates;
4
5 // Method to get all the generic categories
6 final categories = GenericCategories.categories;
7
8 SearchPage({super.key, required this.controller, required this.coordinates});
9
10 @override
11 State<SearchPage> createState() => _SearchPageState();
12}
13
14class _SearchPageState extends State<SearchPage> {
15 final TextEditingController _textController = TextEditingController();
16 List<Landmark> landmarks = [];
17 List<LandmarkCategory> selectedCategories = [];
18
19 @override
20 Widget build(BuildContext context) {
21 return Scaffold(
22 appBar: AppBar(
23 leading: IconButton(
24 onPressed: _onLeadingPressed,
25 icon: const Icon(CupertinoIcons.arrow_left)),
26 title: const Text("Search Category"),
27 backgroundColor: Colors.deepPurple[900],
28 foregroundColor: Colors.white,
29 actions: [
30 if (landmarks.isEmpty)
31 IconButton(
32 onPressed: () => _onSubmitted(_textController.text),
33 icon: const Icon(Icons.search))
34 ],
35 ),
36 body: Column(
37 children: [
38 if (landmarks.isEmpty)
39 Padding(
40 padding: const EdgeInsets.all(8.0),
41 child: TextField(
42 controller: _textController,
43 cursorColor: Colors.deepPurple[900],
44 decoration: const InputDecoration(
45 hintText: 'Enter text',
46 hintStyle: TextStyle(color: Colors.black),
47 focusedBorder: UnderlineInputBorder(
48 borderSide: BorderSide(color: Colors.deepPurple, width: 2.0),
49 ),
50 ),
51 ),
52 ),
53 if (landmarks.isEmpty)
54 Expanded(
55 child: ListView.separated(
56 padding: EdgeInsets.zero,
57 itemCount: widget.categories.length,
58 controller: ScrollController(),
59 separatorBuilder: (context, index) => const Divider(
60 indent: 50,
61 height: 0,
62 ),
63 itemBuilder: (context, index) {
64 return CategoryItem(
65 onTap: () => _onCategoryTap(index),
66 category: widget.categories[index],
67 categoryIcon: widget.categories[index].getImage(size: Size(200, 200)),
68 );
69 },
70 ),
71 ),
72 if (landmarks.isNotEmpty)
73 Expanded(
74 child: ListView.separated(
75 padding: EdgeInsets.zero,
76 itemCount: landmarks.length,
77 controller: ScrollController(),
78 separatorBuilder: (context, index) => const Divider(
79 indent: 50,
80 height: 0,
81 ),
82 itemBuilder: (context, index) {
83 final lmk = landmarks.elementAt(index);
84 return SearchResultItem(landmark: lmk);
85 },
86 ),
87 ),
88 const SizedBox(height: 5),
89 ],
90 ),
91 );
92 }
93
94 // Search method
95 Future<void> search(String text, Coordinates coordinates, SearchPreferences preferences) async {
96 // Implementation of search functionality
97 }
98
99 void _onLeadingPressed() {
100 // Handle back navigation
101 }
102
103 void _onCategoryTap(int index) {
104 // Handle category selection
105 }
106}
107
108// Class for the categories.
109class CategoryItem extends StatefulWidget {
110 final LandmarkCategory category;
111 final Uint8List categoryIcon;
112 final VoidCallback onTap;
113
114 const CategoryItem({
115 super.key,
116 required this.category,
117 required this.onTap,
118 required this.categoryIcon
119 });
120
121 @override
122 State<CategoryItem> createState() => _CategoryItemState();
123}
124
125class _CategoryItemState extends State<CategoryItem> {
126 bool _isSelected = false;
127
128 @override
129 Widget build(BuildContext context) {
130 return ListTile(
131 onTap: () {
132 widget.onTap();
133 setState(() {
134 _isSelected = !_isSelected;
135 });
136 },
137 leading: Container(
138 padding: const EdgeInsets.all(8),
139 width: 50,
140 height: 50,
141 child: Image.memory(widget.categoryIcon),
142 ),
143 title: Text(widget.category.name,
144 style: const TextStyle(color: Colors.black, fontSize: 16, fontWeight: FontWeight.w600)),
145 trailing: (_isSelected)
146 ? const SizedBox(
147 width: 50,
148 child: Icon(
149 Icons.check,
150 color: Colors.grey,
151 ),
152 )
153 : null);
154 }
155}
156
157// Class for the search results.
158class SearchResultItem extends StatefulWidget {
159 final Landmark landmark;
160
161 const SearchResultItem({super.key, required this.landmark});
162
163 @override
164 State<SearchResultItem> createState() => _SearchResultItemState();
165}
166
167class _SearchResultItemState extends State<SearchResultItem> {
168 @override
169 Widget build(BuildContext context) {
170 return ListTile(
171 onTap: () => Navigator.of(context).pop(widget.landmark),
172 leading: Container(
173 padding: const EdgeInsets.all(8),
174 width: 50,
175 child: Image.memory(
176 widget.landmark.getImage(),
177 ),
178 ),
179 title: Text(
180 widget.landmark.name,
181 overflow: TextOverflow.fade,
182 style: const TextStyle(
183 color: Colors.black, fontSize: 14, fontWeight: FontWeight.w400),
184 maxLines: 2,
185 ),
186 subtitle: Text(
187 widget.landmark.getFormattedDistance() + widget.landmark.getAddress(),
188 overflow: TextOverflow.ellipsis,
189 style: const TextStyle(
190 color: Colors.black, fontSize: 14, fontWeight: FontWeight.w400),
191 ),
192 );
193 }
194}